mirror of https://github.com/mgba-emu/mgba.git
Example: Add an example client/server setup showing how to write a frontend
This commit is contained in:
parent
d59ef1c66a
commit
7ff5c3a905
|
@ -25,6 +25,7 @@ if(APPLE)
|
|||
endif()
|
||||
set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool")
|
||||
set(BUILD_TEST OFF CACHE BOOL "Build testing harness")
|
||||
set(BUILD_EXAMPLE OFF CACHE BOOL "Build example frontends")
|
||||
set(BUILD_STATIC OFF CACHE BOOL "Build a static library")
|
||||
set(BUILD_SHARED ON CACHE BOOL "Build a shared library")
|
||||
set(SKIP_LIBRARY OFF CACHE BOOL "Skip building the library (useful for only building libretro or OpenEmu cores)")
|
||||
|
@ -658,6 +659,20 @@ if(BUILD_TEST)
|
|||
install(TARGETS ${BINARY_NAME}-fuzz DESTINATION bin COMPONENT ${BINARY_NAME}-test)
|
||||
endif()
|
||||
|
||||
if(BUILD_EXAMPLE)
|
||||
add_executable(${BINARY_NAME}-example-server ${CMAKE_SOURCE_DIR}/src/platform/example/client-server/server.c)
|
||||
target_link_libraries(${BINARY_NAME}-example-server ${BINARY_NAME})
|
||||
set_target_properties(${BINARY_NAME}-example-server PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
|
||||
|
||||
if(BUILD_SDL)
|
||||
add_executable(${BINARY_NAME}-example-client ${CMAKE_SOURCE_DIR}/src/platform/example/client-server/client.c)
|
||||
target_link_libraries(${BINARY_NAME}-example-client ${BINARY_NAME} ${SDL_LIBRARY} ${SDLMAIN_LIBRARY} ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
|
||||
set_target_properties(${BINARY_NAME}-example-client PROPERTIES
|
||||
COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}"
|
||||
INCLUDE_DIRECTORIES "${SDL_INCLUDE_DIR};${CMAKE_SOURCE_DIR}/src")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Packaging
|
||||
set(CPACK_PACKAGE_VERSION ${VERSION_STRING})
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${LIB_VERSION_MAJOR})
|
||||
|
@ -724,6 +739,7 @@ message(STATUS " Qt: ${BUILD_QT}")
|
|||
message(STATUS " SDL (${SDL_VERSION}): ${BUILD_SDL}")
|
||||
message(STATUS " Profiling: ${BUILD_PERF}")
|
||||
message(STATUS " Test harness: ${BUILD_TEST}")
|
||||
message(STATUS " Examples: ${BUILD_EXAMPLE}")
|
||||
message(STATUS "Cores:")
|
||||
message(STATUS " Libretro core: ${BUILD_LIBRETRO}")
|
||||
if(APPLE)
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
/* Copyright (c) 2013-2016 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "core/core.h"
|
||||
#include "util/socket.h"
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
#define DEFAULT_PORT 13721
|
||||
|
||||
int main() {
|
||||
SocketSubsystemInit();
|
||||
struct Address serverIP = {
|
||||
.version = IPV4,
|
||||
.ipv4 = 0x7F000001
|
||||
};
|
||||
Socket server = SocketConnectTCP(DEFAULT_PORT, &serverIP);
|
||||
if (SOCKET_FAILED(server)) {
|
||||
SocketSubsystemDeinit();
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned width, height, bpp;
|
||||
SocketRecv(server, &width, sizeof(width));
|
||||
SocketRecv(server, &height, sizeof(height));
|
||||
SocketRecv(server, &bpp, sizeof(bpp));
|
||||
width = ntohl(width);
|
||||
height = ntohl(height);
|
||||
if (ntohl(bpp) != BYTES_PER_PIXEL) {
|
||||
SocketClose(server);
|
||||
SocketSubsystemDeinit();
|
||||
return 1;
|
||||
}
|
||||
ssize_t bufferSize = width * height * BYTES_PER_PIXEL;
|
||||
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
#ifdef COLOR_16_BIT
|
||||
SDL_SetVideoMode(width, height, 16, SDL_DOUBLEBUF | SDL_HWSURFACE);
|
||||
#else
|
||||
SDL_SetVideoMode(width, height, 32, SDL_DOUBLEBUF | SDL_HWSURFACE);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_Window* window = SDL_CreateWindow(projectName, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_OPENGL);
|
||||
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
|
||||
|
||||
Uint32 pixfmt;
|
||||
#ifdef COLOR_16_BIT
|
||||
#ifdef COLOR_5_6_5
|
||||
pixfmt = SDL_PIXELFORMAT_RGB565;
|
||||
#else
|
||||
pixfmt = SDL_PIXELFORMAT_ABGR1555;
|
||||
#endif
|
||||
#else
|
||||
pixfmt = SDL_PIXELFORMAT_ABGR8888;
|
||||
#endif
|
||||
SDL_Texture* sdlTex = SDL_CreateTexture(renderer, pixfmt, SDL_TEXTUREACCESS_STREAMING, width, height);
|
||||
#endif
|
||||
|
||||
|
||||
SDL_Event event;
|
||||
#if !SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_Surface* surface = SDL_GetVideoSurface();
|
||||
#endif
|
||||
|
||||
int keys = 0;
|
||||
while (true) {
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type == SDL_QUIT) {
|
||||
SocketClose(server);
|
||||
break;
|
||||
}
|
||||
if (event.type != SDL_KEYDOWN && event.type != SDL_KEYUP) {
|
||||
continue;
|
||||
}
|
||||
int key = 0;
|
||||
switch (event.key.keysym.sym) {
|
||||
case SDLK_x:
|
||||
key = 1;
|
||||
break;
|
||||
case SDLK_z:
|
||||
key = 2;
|
||||
break;
|
||||
case SDLK_BACKSPACE:
|
||||
key = 4;
|
||||
break;
|
||||
case SDLK_RETURN:
|
||||
key = 8;
|
||||
break;
|
||||
case SDLK_RIGHT:
|
||||
key = 16;
|
||||
break;
|
||||
case SDLK_LEFT:
|
||||
key = 32;
|
||||
break;
|
||||
case SDLK_UP:
|
||||
key = 64;
|
||||
break;
|
||||
case SDLK_DOWN:
|
||||
key = 128;
|
||||
break;
|
||||
case SDLK_s:
|
||||
key = 256;
|
||||
break;
|
||||
case SDLK_a:
|
||||
key = 512;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (event.type == SDL_KEYDOWN) {
|
||||
keys |= key;
|
||||
} else {
|
||||
keys &= ~key;
|
||||
}
|
||||
}
|
||||
uint16_t keysNO = htons(keys);
|
||||
if (SocketSend(server, &keysNO, sizeof(keysNO)) != sizeof(keysNO)) {
|
||||
break;
|
||||
}
|
||||
void* pixels;
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
int pitch;
|
||||
SDL_LockTexture(sdlTex, NULL, &pixels, &pitch);
|
||||
#else
|
||||
SDL_LockSurface(surface);
|
||||
pixels = surface->pixels;
|
||||
#endif
|
||||
|
||||
ssize_t expected = bufferSize;
|
||||
ssize_t gotten;
|
||||
while ((gotten = SocketRecv(server, pixels, expected)) != expected) {
|
||||
if (gotten < 0) {
|
||||
break;
|
||||
}
|
||||
pixels = (void*) ((uintptr_t) pixels + gotten);
|
||||
expected -= gotten;
|
||||
}
|
||||
if (gotten < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 0)
|
||||
SDL_UnlockTexture(sdlTex);
|
||||
SDL_RenderCopy(renderer, sdlTex, NULL, NULL);
|
||||
SDL_RenderPresent(renderer);
|
||||
#else
|
||||
SDL_UnlockSurface(surface);
|
||||
SDL_Flip(surface);
|
||||
#endif
|
||||
}
|
||||
SocketSubsystemDeinit();
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
// This source file is placed into the public domain.
|
||||
#include "core/core.h"
|
||||
#include "platform/commandline.h"
|
||||
#include "util/socket.h"
|
||||
|
||||
#define DEFAULT_PORT 13721
|
||||
|
||||
static bool _mExampleRun(const struct mArguments* args, Socket client);
|
||||
static void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args);
|
||||
|
||||
static int _logLevel = 0;
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
bool didFail = false;
|
||||
|
||||
// Arguments from the command line are parsed by the parseArguments function.
|
||||
// The NULL here shows that we don't give it any arguments beyond the default ones.
|
||||
struct mArguments args = {};
|
||||
bool parsed = parseArguments(&args, argc, argv, NULL);
|
||||
if (!parsed || args.showHelp) {
|
||||
// If parsing failed, or the user passed --help, show usage.
|
||||
usage(argv[0], NULL);
|
||||
didFail = !parsed;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (args.showVersion) {
|
||||
// If the user passed --version, show version.
|
||||
version(argv[0]);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Set up a logger. The default logger prints everything to STDOUT, which is not usually desirable.
|
||||
struct mLogger logger = { .log = _log };
|
||||
mLogSetDefaultLogger(&logger);
|
||||
|
||||
// Initialize the socket layer and listen on the default port for this protocol.
|
||||
SocketSubsystemInit();
|
||||
Socket sock = SocketOpenTCP(DEFAULT_PORT, NULL);
|
||||
if (SOCKET_FAILED(sock) || SOCKET_FAILED(SocketListen(sock, 0))) {
|
||||
SocketSubsystemDeinit();
|
||||
didFail = true;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// We only grab one client.
|
||||
Socket client = SocketAccept(sock, NULL);
|
||||
if (SOCKET_FAILED(client)) {
|
||||
SocketClose(sock);
|
||||
SocketSubsystemDeinit();
|
||||
didFail = true;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Run the server
|
||||
didFail = _mExampleRun(&args, client);
|
||||
|
||||
// Clean up the sockets.
|
||||
SocketClose(client);
|
||||
SocketClose(sock);
|
||||
SocketSubsystemDeinit();
|
||||
|
||||
cleanup:
|
||||
freeArguments(&args);
|
||||
|
||||
return didFail;
|
||||
}
|
||||
|
||||
bool _mExampleRun(const struct mArguments* args, Socket client) {
|
||||
// First, we need to find the mCore that's appropriate for this type of file.
|
||||
// If one doesn't exist, it returns NULL and we can't continue.
|
||||
struct mCore* core = mCoreFind(args->fname);
|
||||
if (!core) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Initialize the received core.
|
||||
core->init(core);
|
||||
|
||||
// Get the dimensions required for this core and send them to the client.
|
||||
unsigned width, height;
|
||||
core->desiredVideoDimensions(core, &width, &height);
|
||||
ssize_t bufferSize = width * height * BYTES_PER_PIXEL;
|
||||
uint32_t sendNO;
|
||||
sendNO = htonl(width);
|
||||
SocketSend(client, &sendNO, sizeof(sendNO));
|
||||
sendNO = htonl(height);
|
||||
SocketSend(client, &sendNO, sizeof(sendNO));
|
||||
sendNO = htonl(BYTES_PER_PIXEL);
|
||||
SocketSend(client, &sendNO, sizeof(sendNO));
|
||||
|
||||
// Create a video buffer and tell the core to use it.
|
||||
// If a core isn't told to use a video buffer, it won't render any graphics.
|
||||
// This may be useful in situations where everything except for displayed
|
||||
// output is desired.
|
||||
void* videoOutputBuffer = malloc(bufferSize);
|
||||
core->setVideoBuffer(core, videoOutputBuffer, width);
|
||||
|
||||
// Tell the core to actually load the file.
|
||||
mCoreLoadFile(core, args->fname);
|
||||
|
||||
// Initialize the configuration system and load any saved settings for
|
||||
// this frontend. The second argument to mCoreConfigInit should either be
|
||||
// the name of the frontend, or NULL if you're not loading any saved
|
||||
// settings from disk.
|
||||
mCoreConfigInit(&core->config, "client-server");
|
||||
mCoreConfigLoad(&core->config);
|
||||
|
||||
// Take any settings overrides from the command line and make sure they get
|
||||
// loaded into the config sustem, as well as manually overriding the
|
||||
// "idleOptimization" setting to ensure cores that can detect idle loops
|
||||
// will attempt the detection.
|
||||
applyArguments(args, NULL, &core->config);
|
||||
mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "detect");
|
||||
|
||||
// Tell the core to apply the configuration in the associated config object.
|
||||
mCoreLoadConfig(core);
|
||||
|
||||
// Set our logging level to be the logLevel in the configuration object.
|
||||
mCoreConfigGetIntValue(&core->config, "logLevel", &_logLevel);
|
||||
|
||||
// Reset the core. This is needed before it can run.
|
||||
core->reset(core);
|
||||
|
||||
uint16_t inputNO;
|
||||
while (SocketRecv(client, &inputNO, sizeof(inputNO)) == sizeof(inputNO)) {
|
||||
// After receiving the keys from the client, tell the core that these are
|
||||
// the keys for the current input.
|
||||
core->setKeys(core, ntohs(inputNO));
|
||||
|
||||
// Emulate a single frame.
|
||||
core->runFrame(core);
|
||||
|
||||
// Send back the video buffer.
|
||||
if (SocketSend(client, videoOutputBuffer, bufferSize) != bufferSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Deinitialization associated with the core.
|
||||
mCoreConfigDeinit(&core->config);
|
||||
core->deinit(core);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) {
|
||||
// We don't need the logging object, so we call UNUSED to ensure there's no warning.
|
||||
UNUSED(log);
|
||||
// The level parameter is a bitmask that we can easily filter.
|
||||
if (level & _logLevel) {
|
||||
// Categories are registered at runtime, but the name can be found
|
||||
// through a simple lookup.
|
||||
printf("%s: ", mLogCategoryName(category));
|
||||
|
||||
// We get a format string and a varargs context from the core, so we
|
||||
// need to use the v* version of printf.
|
||||
vprintf(format, args);
|
||||
|
||||
// The format strings do NOT include a newline, so we need to
|
||||
// append it ourself.
|
||||
putchar('\n');
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue