Example: Add an example client/server setup showing how to write a frontend

This commit is contained in:
Jeffrey Pfau 2016-04-27 22:18:08 -07:00
parent d59ef1c66a
commit 7ff5c3a905
3 changed files with 338 additions and 0 deletions

View File

@ -25,6 +25,7 @@ if(APPLE)
endif() endif()
set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool") set(BUILD_PERF OFF CACHE BOOL "Build performance profiling tool")
set(BUILD_TEST OFF CACHE BOOL "Build testing harness") 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_STATIC OFF CACHE BOOL "Build a static library")
set(BUILD_SHARED ON CACHE BOOL "Build a shared 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)") 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) install(TARGETS ${BINARY_NAME}-fuzz DESTINATION bin COMPONENT ${BINARY_NAME}-test)
endif() 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 # Packaging
set(CPACK_PACKAGE_VERSION ${VERSION_STRING}) set(CPACK_PACKAGE_VERSION ${VERSION_STRING})
set(CPACK_PACKAGE_VERSION_MAJOR ${LIB_VERSION_MAJOR}) 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 " SDL (${SDL_VERSION}): ${BUILD_SDL}")
message(STATUS " Profiling: ${BUILD_PERF}") message(STATUS " Profiling: ${BUILD_PERF}")
message(STATUS " Test harness: ${BUILD_TEST}") message(STATUS " Test harness: ${BUILD_TEST}")
message(STATUS " Examples: ${BUILD_EXAMPLE}")
message(STATUS "Cores:") message(STATUS "Cores:")
message(STATUS " Libretro core: ${BUILD_LIBRETRO}") message(STATUS " Libretro core: ${BUILD_LIBRETRO}")
if(APPLE) if(APPLE)

View File

@ -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;
}

View File

@ -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');
}
}