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)

This commit is contained in:
Miodrag Milanovic 2013-08-12 13:56:55 +00:00
parent 59837a1e7d
commit 5f184e36d6
7 changed files with 341 additions and 53 deletions

2
.gitattributes vendored
View File

@ -1942,6 +1942,8 @@ src/emu/video/video.mak svneol=native#text/plain
src/emu/video/vooddefs.h svneol=native#text/plain
src/emu/video/voodoo.c svneol=native#text/plain
src/emu/video/voodoo.h svneol=native#text/plain
src/emu/webengine.c svneol=native#text/plain
src/emu/webengine.h svneol=native#text/plain
src/ldplayer/layout/pr8210.lay svneol=native#text/plain
src/ldplayer/ldplayer.c svneol=native#text/plain
src/ldplayer/ldplayer.lst svneol=native#text/plain

View File

@ -131,6 +131,7 @@ EMUOBJS = \
$(EMUOBJ)/debug/textbuf.o \
$(EMUOBJ)/debugint/debugint.o \
$(EMUOBJ)/profiler.o \
$(EMUOBJ)/webengine.o \
$(OSDOBJ)/osdepend.o \
$(OSDOBJ)/osdnet.o

View File

@ -84,8 +84,7 @@
#include "crsshair.h"
#include "validity.h"
#include "debug/debugcon.h"
#include "web/mongoose.h"
#include "web/json/json.h"
#include "webengine.h"
#include <time.h>
@ -132,33 +131,6 @@ int mame_is_valid_machine(running_machine &machine)
return (&machine == global_machine);
}
// This function will be called by mongoose on every new request.
static int begin_request_handler(struct mg_connection *conn) {
const struct mg_request_info *request_info = mg_get_request_info(conn);
if (!strcmp(request_info->uri, "/hello")) {
Json::Value data;
data["key1"] = "data1";
data["key2"] = "data2";
data["key3"] = "data3";
data["key4"] = "data4";
Json::FastWriter writer;
const char *json = writer.write(data).c_str();
// Send HTTP reply to the client
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Content-Length: %d\r\n" // Always set Content-Length
"\r\n"
"%s",
(int)strlen(json), json);
// Returning non-zero tells mongoose that our function has replied to
// the client, and mongoose should not send client any more data.
return 1;
}
return 0;
}
/*-------------------------------------------------
mame_execute - run the core emulation
-------------------------------------------------*/
@ -172,27 +144,12 @@ int mame_execute(emu_options &options, osd_interface &osd)
if (options.verbose())
print_verbose = true;
struct mg_context *ctx = NULL;
struct mg_callbacks callbacks;
// List of options. Last element must be NULL.
const char *web_options[] = {
"listening_ports", options.http_port(),
"document_root", options.http_path(),
NULL
};
// Prepare callbacks structure.
memset(&callbacks, 0, sizeof(callbacks));
callbacks.begin_request = begin_request_handler;
// Start the web server.
if (options.http())
ctx = mg_start(&callbacks, NULL, web_options);
// loop across multiple hard resets
bool exit_pending = false;
int error = MAMERR_NONE;
web_engine web(options);
while (error == MAMERR_NONE && !exit_pending)
{
// if no driver, use the internal empty driver
@ -230,7 +187,9 @@ int mame_execute(emu_options &options, osd_interface &osd)
// looooong term: remove this
global_machine = &machine;
web.set_machine(machine);
web.push_message("update_machine");
// run the machine
error = machine.run(firstrun);
firstrun = false;
@ -247,10 +206,6 @@ int mame_execute(emu_options &options, osd_interface &osd)
// machine will go away when we exit scope
global_machine = NULL;
}
// Stop the server.
if (options.http())
mg_stop(ctx);
// return an error
return error;
}

229
src/emu/webengine.c Normal file
View File

@ -0,0 +1,229 @@
/***************************************************************************
webengine.c
Handle MAME internal web server.
****************************************************************************
Copyright Miodrag Milanovic
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name 'MAME' nor the names of its contributors may be
used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY MIODRAG MILANOVIC ''AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MIODRAG MILANOVIC BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
***************************************************************************/
#include "emu.h"
#include "emuopts.h"
#include "webengine.h"
#include "web/mongoose.h"
#include "web/json/json.h"
//**************************************************************************
// WEB ENGINE
//**************************************************************************
void web_engine::websocket_ready_handler(struct mg_connection *conn) {
static const char *message = "update_machine";
mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, message, strlen(message));
m_websockets.append(*global_alloc(simple_list_wrapper<mg_connection>(conn)));
}
// Arguments:
// flags: first byte of websocket frame, see websocket RFC,
// http://tools.ietf.org/html/rfc6455, section 5.2
// data, data_len: payload data. Mask, if any, is already applied.
int web_engine::websocket_data_handler(struct mg_connection *conn, int flags,
char *data, size_t data_len)
{
// just Echo example for now
if ((flags & 0x0f) == WEBSOCKET_OPCODE_TEXT)
mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, data, data_len);
// Returning zero means stoping websocket conversation.
// Close the conversation if client has sent us "exit" string.
return memcmp(data, "exit", 4);
}
// This function will be called by mongoose on every new request.
int web_engine::begin_request_handler(struct mg_connection *conn)
{
const struct mg_request_info *request_info = mg_get_request_info(conn);
if (!strncmp(request_info->uri, "/json/",6))
{
if (!strcmp(request_info->uri, "/json/game"))
{
Json::Value data;
data["name"] = m_machine->system().name;
data["description"] = m_machine->system().description;
data["year"] = m_machine->system().year;
data["manufacturer"] = m_machine->system().manufacturer;
data["parent"] = m_machine->system().parent;
data["source_file"] = m_machine->system().source_file;
data["flags"] = m_machine->system().flags;
Json::FastWriter writer;
const char *json = writer.write(data).c_str();
// Send HTTP reply to the client
mg_printf(conn,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Content-Length: %d\r\n" // Always set Content-Length
"\r\n"
"%s",
(int)strlen(json), json);
// Returning non-zero tells mongoose that our function has replied to
// the client, and mongoose should not send client any more data.
mg_close_connection(conn);
return 1;
}
}
return 0;
}
void *web_engine::websocket_keepalive()
{
while(!m_exiting_core)
{
osd_ticks_t curtime = osd_ticks();
if ((curtime - m_lastupdatetime) > osd_ticks_per_second() * 5)
{
m_lastupdatetime = curtime;
for (simple_list_wrapper<mg_connection> *curitem = m_websockets.first(); curitem != NULL; curitem = curitem->next())
{
mg_websocket_write(curitem->object(), WEBSOCKET_OPCODE_PING, NULL, 0);
}
}
osd_sleep(osd_ticks_per_second()/5);
}
return NULL;
}
//-------------------------------------------------
// static callbacks
//-------------------------------------------------
static void websocket_ready_handler_static(struct mg_connection *conn)
{
const struct mg_request_info *request_info = mg_get_request_info(conn);
web_engine *engine = downcast<web_engine *>(request_info->user_data);
engine->websocket_ready_handler(conn);
}
static int websocket_data_handler_static(struct mg_connection *conn, int flags,
char *data, size_t data_len)
{
const struct mg_request_info *request_info = mg_get_request_info(conn);
web_engine *engine = downcast<web_engine *>(request_info->user_data);
return engine->websocket_data_handler(conn, flags, data, data_len);
}
static int begin_request_handler_static(struct mg_connection *conn)
{
const struct mg_request_info *request_info = mg_get_request_info(conn);
web_engine *engine = downcast<web_engine *>(request_info->user_data);
return engine->begin_request_handler(conn);
}
static void *websocket_keepalive_static(void *thread_func_param)
{
web_engine *engine = downcast<web_engine *>(thread_func_param);
return engine->websocket_keepalive();
}
//-------------------------------------------------
// web_engine - constructor
//-------------------------------------------------
web_engine::web_engine(emu_options &options)
: m_options(options),
m_machine(NULL),
m_ctx(NULL),
m_lastupdatetime(0),
m_exiting_core(false)
{
struct mg_callbacks callbacks;
// List of options. Last element must be NULL.
const char *web_options[] = {
"listening_ports", options.http_port(),
"document_root", options.http_path(),
NULL
};
// Prepare callbacks structure.
memset(&callbacks, 0, sizeof(callbacks));
callbacks.begin_request = begin_request_handler_static;
callbacks.websocket_ready = websocket_ready_handler_static;
callbacks.websocket_data = websocket_data_handler_static;
// Start the web server.
if (m_options.http()) {
m_ctx = mg_start(&callbacks, this, web_options);
mg_start_thread(websocket_keepalive_static, this);
}
}
//-------------------------------------------------
// ~web_engine - destructor
//-------------------------------------------------
web_engine::~web_engine()
{
if (m_options.http())
close();
}
//-------------------------------------------------
// close - close and cleanup of lua engine
//-------------------------------------------------
void web_engine::close()
{
m_exiting_core = 1;
osd_sleep(osd_ticks_per_second()/5);
for (simple_list_wrapper<mg_connection> *curitem = m_websockets.first(); curitem != NULL; curitem = curitem->next())
{
mg_websocket_write(curitem->object(), WEBSOCKET_OPCODE_CONNECTION_CLOSE, NULL, 0);
}
// Stop the server.
mg_stop(m_ctx);
}
void web_engine::push_message(const char *message)
{
for (simple_list_wrapper<mg_connection> *curitem = m_websockets.first(); curitem != NULL; curitem = curitem->next())
{
mg_websocket_write(curitem->object(), WEBSOCKET_OPCODE_TEXT, message, strlen(message));
}
}

74
src/emu/webengine.h Normal file
View File

@ -0,0 +1,74 @@
/***************************************************************************
webengine.h
Handle MAME internal web server.
****************************************************************************
Copyright Miodrag Milanovic
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name 'MAME' nor the names of its contributors may be
used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY MIODRAG MILANOVIC ''AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL MIODRAG MILANOVIC BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
***************************************************************************/
#pragma once
#ifndef __WEB_ENGINE_H__
#define __WEB_ENGINE_H__
struct mg_context; // Handle for the HTTP service itself
struct mg_connection; // Handle for the individual connection
class web_engine
{
public:
// construction/destruction
web_engine(emu_options &options);
~web_engine();
void push_message(const char *message);
void close();
void set_machine(running_machine &machine) { m_machine = &machine; }
void websocket_ready_handler(struct mg_connection *conn);
int websocket_data_handler(struct mg_connection *conn, int flags, char *data, size_t data_len);
int begin_request_handler(struct mg_connection *conn);
void *websocket_keepalive();
private:
// internal state
emu_options & m_options;
running_machine * m_machine;
struct mg_context * m_ctx;
osd_ticks_t m_lastupdatetime;
bool m_exiting_core;
simple_list<simple_list_wrapper<mg_connection> > m_websockets;
};
#endif /* __web_engine_H__ */

View File

@ -19,6 +19,7 @@
// THE SOFTWARE.
#define NO_SSL
#define USE_WEBSOCKET
#if defined(_WIN32)
#define SETSOCKOPT_CAST const char *
@ -4052,7 +4053,7 @@ static void read_websocket(struct mg_connection *conn) {
if (header_len > 0) {
// Allocate space to hold websocket payload
data = mem;
if (data_len > sizeof(mem) && (data = malloc(data_len)) == NULL) {
if (data_len > sizeof(mem) && (data = (char*)malloc(data_len)) == NULL) {
// Allocation failed, exit the loop and then close the connection
// TODO: notify user about the failure
break;

View File

@ -11,6 +11,31 @@
<link href="css/default.ultimate.css" media="screen" rel="stylesheet" type="text/css" />
</head>
<body>
<script language="javascript" type="text/javascript">
var writeToScreen = function(message) {
var div = document.createElement('div');
div.innerHTML = message;
document.getElementById('output').appendChild(div);
};
window.onload = function() {
var url = 'ws://localhost:8080/foo';
websocket = new WebSocket(url);
websocket.onopen = function(ev) {
writeToScreen('<span style="color: green;">CONNECTED</span>');
};
websocket.onclose = function(ev) {
writeToScreen('<span style="color: white;">DISCONNECTED</span>');
};
websocket.onmessage = function(ev) {
writeToScreen('<span style="color: blue;">RESPONSE: ' + ev.data +
' </span>');
};
websocket.onerror = function(ev) {
writeToScreen('<span style="color: red; ">ERROR: </span> ' + ev.data);
};
};
</script>
<div data-role="page">
<div data-role="header">
<img src="images/logo-mame-small.png"/>
@ -46,6 +71,7 @@
</li>
<li id="n-debugger"><a href="./">Debugger</a></li>
</ul>
<br/><br/><div id="output"></div>
</div>
</body>
</html>