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