Example: Add an example client/server setup showing how to write a frontend
Jeffrey Pfau jeffrey@endrift.com
Wed, 27 Apr 2016 22:18:08 -0700
3 files changed,
338 insertions(+),
0 deletions(-)
M
CMakeLists.txt
→
CMakeLists.txt
@@ -25,6 +25,7 @@ set(BUILD_OPENEMU OFF CACHE BOOL "Build OpenEmu core")
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 @@ set_target_properties(${BINARY_NAME}-fuzz PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FEATURE_DEFINES};${FUNCTION_DEFINES}")
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)
A
src/platform/example/client-server/client.c
@@ -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; +}
A
src/platform/example/client-server/server.c
@@ -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'); + } +}