Previous 199869 Revisions Next

r24858 Monday 12th August, 2013 at 13:56:55 UTC by Miodrag Milanović
Moved webengine into separate file, added callbacks for websockets and made example for triggering ui on driver change, also added json for game driver (nw)
[src/emu]emu.mak mame.c webengine.c* webengine.h*
[src/lib/web]mongoose.c
[web]index.html

trunk/src/lib/web/mongoose.c
r24857r24858
1919// THE SOFTWARE.
2020
2121#define NO_SSL
22#define USE_WEBSOCKET
2223
2324#if defined(_WIN32)
2425#define SETSOCKOPT_CAST const char *
r24857r24858
40524053    if (header_len > 0) {
40534054      // Allocate space to hold websocket payload
40544055      data = mem;
4055      if (data_len > sizeof(mem) && (data = malloc(data_len)) == NULL) {
4056      if (data_len > sizeof(mem) && (data = (char*)malloc(data_len)) == NULL) {
40564057        // Allocation failed, exit the loop and then close the connection
40574058        // TODO: notify user about the failure
40584059        break;
trunk/src/emu/mame.c
r24857r24858
8484#include "crsshair.h"
8585#include "validity.h"
8686#include "debug/debugcon.h"
87#include "web/mongoose.h"
88#include "web/json/json.h"
87#include "webengine.h"
8988#include <time.h>
9089
9190
r24857r24858
132131   return (&machine == global_machine);
133132}
134133
135// This function will be called by mongoose on every new request.
136static 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}
162134/*-------------------------------------------------
163135    mame_execute - run the core emulation
164136-------------------------------------------------*/
r24857r24858
172144   if (options.verbose())
173145      print_verbose = true;
174146
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   
193147   // loop across multiple hard resets
194148   bool exit_pending = false;
195149   int error = MAMERR_NONE;
150   
151   web_engine web(options);
152   
196153   while (error == MAMERR_NONE && !exit_pending)
197154   {
198155      // if no driver, use the internal empty driver
r24857r24858
230187
231188      // looooong term: remove this
232189      global_machine = &machine;
233
190     
191      web.set_machine(machine);
192      web.push_message("update_machine");     
234193      // run the machine
235194      error = machine.run(firstrun);
236195      firstrun = false;
r24857r24858
247206      // machine will go away when we exit scope
248207      global_machine = NULL;
249208   }
250
251   // Stop the server.
252   if (options.http())
253      mg_stop(ctx);
254209   // return an error
255210   return error;
256211}
trunk/src/emu/webengine.c
r0r24858
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
50void 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.
60int 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.
73int 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
110void *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//-------------------------------------------------
131static 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
138static 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
146static 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
153static 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
163web_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
200web_engine::~web_engine()
201{
202   if (m_options.http())
203      close();
204}
205
206//-------------------------------------------------
207//  close - close and cleanup of lua engine
208//-------------------------------------------------
209
210void 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
223void 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}
Property changes on: trunk/src/emu/webengine.c
Added: svn:mime-type
   + text/plain
Added: svn:eol-style
   + native
trunk/src/emu/webengine.h
r0r24858
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
45struct mg_context;     // Handle for the HTTP service itself
46struct mg_connection;  // Handle for the individual connection
47
48class web_engine
49{
50public:
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();
64private:
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__ */
Property changes on: trunk/src/emu/webengine.h
Added: svn:eol-style
   + native
Added: svn:mime-type
   + text/plain
trunk/src/emu/emu.mak
r24857r24858
131131   $(EMUOBJ)/debug/textbuf.o \
132132   $(EMUOBJ)/debugint/debugint.o \
133133   $(EMUOBJ)/profiler.o \
134   $(EMUOBJ)/webengine.o \
134135   $(OSDOBJ)/osdepend.o \
135136   $(OSDOBJ)/osdnet.o
136137
trunk/web/index.html
r24857r24858
1111   <link href="css/default.ultimate.css" media="screen" rel="stylesheet" type="text/css" />
1212</head>
1313<body>
14<script language="javascript" type="text/javascript">
15
16  var writeToScreen = function(message) {
17    var div = document.createElement('div');
18    div.innerHTML = message;
19    document.getElementById('output').appendChild(div);
20  };
21  window.onload = function() {
22    var url = 'ws://localhost:8080/foo';
23    websocket = new WebSocket(url);
24    websocket.onopen = function(ev) {
25     writeToScreen('<span style="color: green;">CONNECTED</span>');
26    };
27    websocket.onclose = function(ev) {     
28      writeToScreen('<span style="color: white;">DISCONNECTED</span>');
29    };
30    websocket.onmessage = function(ev) {
31      writeToScreen('<span style="color: blue;">RESPONSE: ' + ev.data +
32                    ' </span>');
33    };
34    websocket.onerror = function(ev) {
35      writeToScreen('<span style="color: red; ">ERROR: </span> ' + ev.data);
36    };
37  };
38</script>
1439<div data-role="page">
1540    <div data-role="header">
1641      <img src="images/logo-mame-small.png"/>
r24857r24858
4671   </li>
4772   <li id="n-debugger"><a href="./">Debugger</a></li>
4873</ul>
74<br/><br/><div id="output"></div>
4975</div>
5076</body>
5177</html>
No newline at end of file

Previous 199869 Revisions Next


© 1997-2024 The MAME Team