src/platform/example/client-server/server.c (view raw)
1// This source file is placed into the public domain.
2#include "core/core.h"
3#include "feature/commandline.h"
4#include "util/socket.h"
5
6#define DEFAULT_PORT 13721
7
8static bool _mExampleRun(const struct mArguments* args, Socket client);
9static void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args);
10
11static int _logLevel = 0;
12
13int main(int argc, char** argv) {
14 bool didFail = false;
15
16 // Arguments from the command line are parsed by the parseArguments function.
17 // The NULL here shows that we don't give it any arguments beyond the default ones.
18 struct mArguments args = {};
19 bool parsed = parseArguments(&args, argc, argv, NULL);
20 // Parsing can succeed without finding a filename, but we need one.
21 if (!args.fname) {
22 parsed = false;
23 }
24 if (!parsed || args.showHelp) {
25 // If parsing failed, or the user passed --help, show usage.
26 usage(argv[0], NULL);
27 didFail = !parsed;
28 goto cleanup;
29 }
30
31 if (args.showVersion) {
32 // If the user passed --version, show version.
33 version(argv[0]);
34 goto cleanup;
35 }
36
37 // Set up a logger. The default logger prints everything to STDOUT, which is not usually desirable.
38 struct mLogger logger = { .log = _log };
39 mLogSetDefaultLogger(&logger);
40
41 // Initialize the socket layer and listen on the default port for this protocol.
42 SocketSubsystemInit();
43 Socket sock = SocketOpenTCP(DEFAULT_PORT, NULL);
44 if (SOCKET_FAILED(sock) || SOCKET_FAILED(SocketListen(sock, 0))) {
45 SocketSubsystemDeinit();
46 didFail = true;
47 goto cleanup;
48 }
49
50 // We only grab one client.
51 Socket client = SocketAccept(sock, NULL);
52 if (SOCKET_FAILED(client)) {
53 SocketClose(sock);
54 SocketSubsystemDeinit();
55 didFail = true;
56 goto cleanup;
57 }
58
59 // Run the server
60 didFail = _mExampleRun(&args, client);
61
62 // Clean up the sockets.
63 SocketClose(client);
64 SocketClose(sock);
65 SocketSubsystemDeinit();
66
67 cleanup:
68 freeArguments(&args);
69
70 return didFail;
71}
72
73bool _mExampleRun(const struct mArguments* args, Socket client) {
74 // First, we need to find the mCore that's appropriate for this type of file.
75 // If one doesn't exist, it returns NULL and we can't continue.
76 struct mCore* core = mCoreFind(args->fname);
77 if (!core) {
78 return false;
79 }
80
81 // Initialize the received core.
82 core->init(core);
83
84 // Get the dimensions required for this core and send them to the client.
85 unsigned width, height;
86 core->desiredVideoDimensions(core, &width, &height);
87 ssize_t bufferSize = width * height * BYTES_PER_PIXEL;
88 uint32_t sendNO;
89 sendNO = htonl(width);
90 SocketSend(client, &sendNO, sizeof(sendNO));
91 sendNO = htonl(height);
92 SocketSend(client, &sendNO, sizeof(sendNO));
93 sendNO = htonl(BYTES_PER_PIXEL);
94 SocketSend(client, &sendNO, sizeof(sendNO));
95
96 // Create a video buffer and tell the core to use it.
97 // If a core isn't told to use a video buffer, it won't render any graphics.
98 // This may be useful in situations where everything except for displayed
99 // output is desired.
100 void* videoOutputBuffer = malloc(bufferSize);
101 core->setVideoBuffer(core, videoOutputBuffer, width);
102
103 // Tell the core to actually load the file.
104 mCoreLoadFile(core, args->fname);
105
106 // Initialize the configuration system and load any saved settings for
107 // this frontend. The second argument to mCoreConfigInit should either be
108 // the name of the frontend, or NULL if you're not loading any saved
109 // settings from disk.
110 mCoreConfigInit(&core->config, "client-server");
111 mCoreConfigLoad(&core->config);
112
113 // Take any settings overrides from the command line and make sure they get
114 // loaded into the config system, as well as manually overriding the
115 // "idleOptimization" setting to ensure cores that can detect idle loops
116 // will attempt the detection.
117 applyArguments(args, NULL, &core->config);
118 mCoreConfigSetDefaultValue(&core->config, "idleOptimization", "detect");
119
120 // Tell the core to apply the configuration in the associated config object.
121 mCoreLoadConfig(core);
122
123 // Set our logging level to be the logLevel in the configuration object.
124 mCoreConfigGetIntValue(&core->config, "logLevel", &_logLevel);
125
126 // Reset the core. This is needed before it can run.
127 core->reset(core);
128
129 uint16_t inputNO;
130 while (SocketRecv(client, &inputNO, sizeof(inputNO)) == sizeof(inputNO)) {
131 // After receiving the keys from the client, tell the core that these are
132 // the keys for the current input.
133 core->setKeys(core, ntohs(inputNO));
134
135 // Emulate a single frame.
136 core->runFrame(core);
137
138 // Send back the video buffer.
139 if (SocketSend(client, videoOutputBuffer, bufferSize) != bufferSize) {
140 break;
141 }
142 }
143
144 // Deinitialization associated with the core.
145 mCoreConfigDeinit(&core->config);
146 core->deinit(core);
147
148 return true;
149}
150
151void _log(struct mLogger* log, int category, enum mLogLevel level, const char* format, va_list args) {
152 // We don't need the logging object, so we call UNUSED to ensure there's no warning.
153 UNUSED(log);
154 // The level parameter is a bitmask that we can easily filter.
155 if (level & _logLevel) {
156 // Categories are registered at runtime, but the name can be found
157 // through a simple lookup.
158 printf("%s: ", mLogCategoryName(category));
159
160 // We get a format string and a varargs context from the core, so we
161 // need to use the v* version of printf.
162 vprintf(format, args);
163
164 // The format strings do NOT include a newline, so we need to
165 // append it ourself.
166 putchar('\n');
167 }
168}