trunk/src/emu/mame.c
| r24857 | r24858 | |
| 84 | 84 | #include "crsshair.h" |
| 85 | 85 | #include "validity.h" |
| 86 | 86 | #include "debug/debugcon.h" |
| 87 | | #include "web/mongoose.h" |
| 88 | | #include "web/json/json.h" |
| 87 | #include "webengine.h" |
| 89 | 88 | #include <time.h> |
| 90 | 89 | |
| 91 | 90 | |
| r24857 | r24858 | |
| 132 | 131 | return (&machine == global_machine); |
| 133 | 132 | } |
| 134 | 133 | |
| 135 | | // This function will be called by mongoose on every new request. |
| 136 | | static int begin_request_handler(struct mg_connection *conn) { |
| 137 | | const struct mg_request_info *request_info = mg_get_request_info(conn); |
| 138 | | if (!strcmp(request_info->uri, "/hello")) { |
| 139 | | Json::Value data; |
| 140 | | data["key1"] = "data1"; |
| 141 | | data["key2"] = "data2"; |
| 142 | | data["key3"] = "data3"; |
| 143 | | data["key4"] = "data4"; |
| 144 | | |
| 145 | | Json::FastWriter writer; |
| 146 | | const char *json = writer.write(data).c_str(); |
| 147 | | // Send HTTP reply to the client |
| 148 | | mg_printf(conn, |
| 149 | | "HTTP/1.1 200 OK\r\n" |
| 150 | | "Content-Type: application/json\r\n" |
| 151 | | "Content-Length: %d\r\n" // Always set Content-Length |
| 152 | | "\r\n" |
| 153 | | "%s", |
| 154 | | (int)strlen(json), json); |
| 155 | | |
| 156 | | // Returning non-zero tells mongoose that our function has replied to |
| 157 | | // the client, and mongoose should not send client any more data. |
| 158 | | return 1; |
| 159 | | } |
| 160 | | return 0; |
| 161 | | } |
| 162 | 134 | /*------------------------------------------------- |
| 163 | 135 | mame_execute - run the core emulation |
| 164 | 136 | -------------------------------------------------*/ |
| r24857 | r24858 | |
| 172 | 144 | if (options.verbose()) |
| 173 | 145 | print_verbose = true; |
| 174 | 146 | |
| 175 | | struct mg_context *ctx = NULL; |
| 176 | | struct mg_callbacks callbacks; |
| 177 | | |
| 178 | | // List of options. Last element must be NULL. |
| 179 | | const char *web_options[] = { |
| 180 | | "listening_ports", options.http_port(), |
| 181 | | "document_root", options.http_path(), |
| 182 | | NULL |
| 183 | | }; |
| 184 | | |
| 185 | | // Prepare callbacks structure. |
| 186 | | memset(&callbacks, 0, sizeof(callbacks)); |
| 187 | | callbacks.begin_request = begin_request_handler; |
| 188 | | |
| 189 | | // Start the web server. |
| 190 | | if (options.http()) |
| 191 | | ctx = mg_start(&callbacks, NULL, web_options); |
| 192 | | |
| 193 | 147 | // loop across multiple hard resets |
| 194 | 148 | bool exit_pending = false; |
| 195 | 149 | int error = MAMERR_NONE; |
| 150 | |
| 151 | web_engine web(options); |
| 152 | |
| 196 | 153 | while (error == MAMERR_NONE && !exit_pending) |
| 197 | 154 | { |
| 198 | 155 | // if no driver, use the internal empty driver |
| r24857 | r24858 | |
| 230 | 187 | |
| 231 | 188 | // looooong term: remove this |
| 232 | 189 | global_machine = &machine; |
| 233 | | |
| 190 | |
| 191 | web.set_machine(machine); |
| 192 | web.push_message("update_machine"); |
| 234 | 193 | // run the machine |
| 235 | 194 | error = machine.run(firstrun); |
| 236 | 195 | firstrun = false; |
| r24857 | r24858 | |
| 247 | 206 | // machine will go away when we exit scope |
| 248 | 207 | global_machine = NULL; |
| 249 | 208 | } |
| 250 | | |
| 251 | | // Stop the server. |
| 252 | | if (options.http()) |
| 253 | | mg_stop(ctx); |
| 254 | 209 | // return an error |
| 255 | 210 | return error; |
| 256 | 211 | } |
trunk/src/emu/webengine.c
| r0 | r24858 | |
| 1 | /*************************************************************************** |
| 2 | |
| 3 | webengine.c |
| 4 | |
| 5 | Handle MAME internal web server. |
| 6 | |
| 7 | **************************************************************************** |
| 8 | |
| 9 | Copyright Miodrag Milanovic |
| 10 | All rights reserved. |
| 11 | |
| 12 | Redistribution and use in source and binary forms, with or without |
| 13 | modification, are permitted provided that the following conditions are |
| 14 | met: |
| 15 | |
| 16 | * Redistributions of source code must retain the above copyright |
| 17 | notice, this list of conditions and the following disclaimer. |
| 18 | * Redistributions in binary form must reproduce the above copyright |
| 19 | notice, this list of conditions and the following disclaimer in |
| 20 | the documentation and/or other materials provided with the |
| 21 | distribution. |
| 22 | * Neither the name 'MAME' nor the names of its contributors may be |
| 23 | used to endorse or promote products derived from this software |
| 24 | without specific prior written permission. |
| 25 | |
| 26 | THIS SOFTWARE IS PROVIDED BY MIODRAG MILANOVIC ''AS IS'' AND ANY EXPRESS OR |
| 27 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 28 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 29 | DISCLAIMED. IN NO EVENT SHALL MIODRAG MILANOVIC BE LIABLE FOR ANY DIRECT, |
| 30 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 31 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| 32 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| 33 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| 34 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING |
| 35 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 36 | POSSIBILITY OF SUCH DAMAGE. |
| 37 | |
| 38 | ***************************************************************************/ |
| 39 | |
| 40 | #include "emu.h" |
| 41 | #include "emuopts.h" |
| 42 | #include "webengine.h" |
| 43 | #include "web/mongoose.h" |
| 44 | #include "web/json/json.h" |
| 45 | |
| 46 | //************************************************************************** |
| 47 | // WEB ENGINE |
| 48 | //************************************************************************** |
| 49 | |
| 50 | void web_engine::websocket_ready_handler(struct mg_connection *conn) { |
| 51 | static const char *message = "update_machine"; |
| 52 | mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, message, strlen(message)); |
| 53 | m_websockets.append(*global_alloc(simple_list_wrapper<mg_connection>(conn))); |
| 54 | } |
| 55 | |
| 56 | // Arguments: |
| 57 | // flags: first byte of websocket frame, see websocket RFC, |
| 58 | // http://tools.ietf.org/html/rfc6455, section 5.2 |
| 59 | // data, data_len: payload data. Mask, if any, is already applied. |
| 60 | int web_engine::websocket_data_handler(struct mg_connection *conn, int flags, |
| 61 | char *data, size_t data_len) |
| 62 | { |
| 63 | // just Echo example for now |
| 64 | if ((flags & 0x0f) == WEBSOCKET_OPCODE_TEXT) |
| 65 | mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, data, data_len); |
| 66 | |
| 67 | // Returning zero means stoping websocket conversation. |
| 68 | // Close the conversation if client has sent us "exit" string. |
| 69 | return memcmp(data, "exit", 4); |
| 70 | } |
| 71 | |
| 72 | // This function will be called by mongoose on every new request. |
| 73 | int web_engine::begin_request_handler(struct mg_connection *conn) |
| 74 | { |
| 75 | const struct mg_request_info *request_info = mg_get_request_info(conn); |
| 76 | if (!strncmp(request_info->uri, "/json/",6)) |
| 77 | { |
| 78 | if (!strcmp(request_info->uri, "/json/game")) |
| 79 | { |
| 80 | Json::Value data; |
| 81 | data["name"] = m_machine->system().name; |
| 82 | data["description"] = m_machine->system().description; |
| 83 | data["year"] = m_machine->system().year; |
| 84 | data["manufacturer"] = m_machine->system().manufacturer; |
| 85 | data["parent"] = m_machine->system().parent; |
| 86 | data["source_file"] = m_machine->system().source_file; |
| 87 | data["flags"] = m_machine->system().flags; |
| 88 | |
| 89 | Json::FastWriter writer; |
| 90 | const char *json = writer.write(data).c_str(); |
| 91 | // Send HTTP reply to the client |
| 92 | mg_printf(conn, |
| 93 | "HTTP/1.1 200 OK\r\n" |
| 94 | "Content-Type: application/json\r\n" |
| 95 | "Content-Length: %d\r\n" // Always set Content-Length |
| 96 | "\r\n" |
| 97 | "%s", |
| 98 | (int)strlen(json), json); |
| 99 | |
| 100 | // Returning non-zero tells mongoose that our function has replied to |
| 101 | // the client, and mongoose should not send client any more data. |
| 102 | mg_close_connection(conn); |
| 103 | return 1; |
| 104 | } |
| 105 | } |
| 106 | return 0; |
| 107 | } |
| 108 | |
| 109 | |
| 110 | void *web_engine::websocket_keepalive() |
| 111 | { |
| 112 | while(!m_exiting_core) |
| 113 | { |
| 114 | osd_ticks_t curtime = osd_ticks(); |
| 115 | if ((curtime - m_lastupdatetime) > osd_ticks_per_second() * 5) |
| 116 | { |
| 117 | m_lastupdatetime = curtime; |
| 118 | for (simple_list_wrapper<mg_connection> *curitem = m_websockets.first(); curitem != NULL; curitem = curitem->next()) |
| 119 | { |
| 120 | mg_websocket_write(curitem->object(), WEBSOCKET_OPCODE_PING, NULL, 0); |
| 121 | } |
| 122 | } |
| 123 | osd_sleep(osd_ticks_per_second()/5); |
| 124 | } |
| 125 | return NULL; |
| 126 | } |
| 127 | |
| 128 | //------------------------------------------------- |
| 129 | // static callbacks |
| 130 | //------------------------------------------------- |
| 131 | static void websocket_ready_handler_static(struct mg_connection *conn) |
| 132 | { |
| 133 | const struct mg_request_info *request_info = mg_get_request_info(conn); |
| 134 | web_engine *engine = downcast<web_engine *>(request_info->user_data); |
| 135 | engine->websocket_ready_handler(conn); |
| 136 | } |
| 137 | |
| 138 | static int websocket_data_handler_static(struct mg_connection *conn, int flags, |
| 139 | char *data, size_t data_len) |
| 140 | { |
| 141 | const struct mg_request_info *request_info = mg_get_request_info(conn); |
| 142 | web_engine *engine = downcast<web_engine *>(request_info->user_data); |
| 143 | return engine->websocket_data_handler(conn, flags, data, data_len); |
| 144 | } |
| 145 | |
| 146 | static int begin_request_handler_static(struct mg_connection *conn) |
| 147 | { |
| 148 | const struct mg_request_info *request_info = mg_get_request_info(conn); |
| 149 | web_engine *engine = downcast<web_engine *>(request_info->user_data); |
| 150 | return engine->begin_request_handler(conn); |
| 151 | } |
| 152 | |
| 153 | static void *websocket_keepalive_static(void *thread_func_param) |
| 154 | { |
| 155 | web_engine *engine = downcast<web_engine *>(thread_func_param); |
| 156 | return engine->websocket_keepalive(); |
| 157 | } |
| 158 | |
| 159 | //------------------------------------------------- |
| 160 | // web_engine - constructor |
| 161 | //------------------------------------------------- |
| 162 | |
| 163 | web_engine::web_engine(emu_options &options) |
| 164 | : m_options(options), |
| 165 | m_machine(NULL), |
| 166 | m_ctx(NULL), |
| 167 | m_lastupdatetime(0), |
| 168 | m_exiting_core(false) |
| 169 | |
| 170 | { |
| 171 | |
| 172 | struct mg_callbacks callbacks; |
| 173 | |
| 174 | // List of options. Last element must be NULL. |
| 175 | const char *web_options[] = { |
| 176 | "listening_ports", options.http_port(), |
| 177 | "document_root", options.http_path(), |
| 178 | NULL |
| 179 | }; |
| 180 | |
| 181 | // Prepare callbacks structure. |
| 182 | memset(&callbacks, 0, sizeof(callbacks)); |
| 183 | callbacks.begin_request = begin_request_handler_static; |
| 184 | callbacks.websocket_ready = websocket_ready_handler_static; |
| 185 | callbacks.websocket_data = websocket_data_handler_static; |
| 186 | |
| 187 | // Start the web server. |
| 188 | if (m_options.http()) { |
| 189 | m_ctx = mg_start(&callbacks, this, web_options); |
| 190 | |
| 191 | mg_start_thread(websocket_keepalive_static, this); |
| 192 | } |
| 193 | |
| 194 | } |
| 195 | |
| 196 | //------------------------------------------------- |
| 197 | // ~web_engine - destructor |
| 198 | //------------------------------------------------- |
| 199 | |
| 200 | web_engine::~web_engine() |
| 201 | { |
| 202 | if (m_options.http()) |
| 203 | close(); |
| 204 | } |
| 205 | |
| 206 | //------------------------------------------------- |
| 207 | // close - close and cleanup of lua engine |
| 208 | //------------------------------------------------- |
| 209 | |
| 210 | void web_engine::close() |
| 211 | { |
| 212 | m_exiting_core = 1; |
| 213 | osd_sleep(osd_ticks_per_second()/5); |
| 214 | for (simple_list_wrapper<mg_connection> *curitem = m_websockets.first(); curitem != NULL; curitem = curitem->next()) |
| 215 | { |
| 216 | mg_websocket_write(curitem->object(), WEBSOCKET_OPCODE_CONNECTION_CLOSE, NULL, 0); |
| 217 | } |
| 218 | // Stop the server. |
| 219 | mg_stop(m_ctx); |
| 220 | } |
| 221 | |
| 222 | |
| 223 | void web_engine::push_message(const char *message) |
| 224 | { |
| 225 | for (simple_list_wrapper<mg_connection> *curitem = m_websockets.first(); curitem != NULL; curitem = curitem->next()) |
| 226 | { |
| 227 | mg_websocket_write(curitem->object(), WEBSOCKET_OPCODE_TEXT, message, strlen(message)); |
| 228 | } |
| 229 | } |
trunk/src/emu/webengine.h
| r0 | r24858 | |
| 1 | /*************************************************************************** |
| 2 | |
| 3 | webengine.h |
| 4 | |
| 5 | Handle MAME internal web server. |
| 6 | |
| 7 | **************************************************************************** |
| 8 | |
| 9 | Copyright Miodrag Milanovic |
| 10 | All rights reserved. |
| 11 | |
| 12 | Redistribution and use in source and binary forms, with or without |
| 13 | modification, are permitted provided that the following conditions are |
| 14 | met: |
| 15 | |
| 16 | * Redistributions of source code must retain the above copyright |
| 17 | notice, this list of conditions and the following disclaimer. |
| 18 | * Redistributions in binary form must reproduce the above copyright |
| 19 | notice, this list of conditions and the following disclaimer in |
| 20 | the documentation and/or other materials provided with the |
| 21 | distribution. |
| 22 | * Neither the name 'MAME' nor the names of its contributors may be |
| 23 | used to endorse or promote products derived from this software |
| 24 | without specific prior written permission. |
| 25 | |
| 26 | THIS SOFTWARE IS PROVIDED BY MIODRAG MILANOVIC ''AS IS'' AND ANY EXPRESS OR |
| 27 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 28 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 29 | DISCLAIMED. IN NO EVENT SHALL MIODRAG MILANOVIC BE LIABLE FOR ANY DIRECT, |
| 30 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 31 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| 32 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
| 33 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| 34 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING |
| 35 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| 36 | POSSIBILITY OF SUCH DAMAGE. |
| 37 | |
| 38 | ***************************************************************************/ |
| 39 | |
| 40 | #pragma once |
| 41 | |
| 42 | #ifndef __WEB_ENGINE_H__ |
| 43 | #define __WEB_ENGINE_H__ |
| 44 | |
| 45 | struct mg_context; // Handle for the HTTP service itself |
| 46 | struct mg_connection; // Handle for the individual connection |
| 47 | |
| 48 | class web_engine |
| 49 | { |
| 50 | public: |
| 51 | // construction/destruction |
| 52 | web_engine(emu_options &options); |
| 53 | ~web_engine(); |
| 54 | |
| 55 | void push_message(const char *message); |
| 56 | void close(); |
| 57 | |
| 58 | void set_machine(running_machine &machine) { m_machine = &machine; } |
| 59 | |
| 60 | void websocket_ready_handler(struct mg_connection *conn); |
| 61 | int websocket_data_handler(struct mg_connection *conn, int flags, char *data, size_t data_len); |
| 62 | int begin_request_handler(struct mg_connection *conn); |
| 63 | void *websocket_keepalive(); |
| 64 | private: |
| 65 | // internal state |
| 66 | emu_options & m_options; |
| 67 | running_machine * m_machine; |
| 68 | struct mg_context * m_ctx; |
| 69 | osd_ticks_t m_lastupdatetime; |
| 70 | bool m_exiting_core; |
| 71 | simple_list<simple_list_wrapper<mg_connection> > m_websockets; |
| 72 | }; |
| 73 | |
| 74 | #endif /* __web_engine_H__ */ |