OXYGENHUB
◆ main retro-remote-debug-controller / core / retro_control.h
9.0 KiB · Objective-C · last changed feat: contract 0.6 — GET/POST /perf, the performance metrics (budget, current frame, windows, zones, raw frames since one, a platform section) and their settings, and /status.capabilities; backend callbacks get_perf and set_perf return allocated JSON (#6) 25 days ago source blame history raw
961b3c61 Andy McCall 1
/* retro_control.h — portable emulator control server (see ../SPEC.md).
2
 *
3
 * A retro-platform emulator implements the small read-only backend below and
4
 * calls retro_control_start(). The shared server (retro_control.c) owns the
5
 * socket loop, HTTP parsing, PPM/JSON encoding, and control flow
4c0b050e Andy McCall ↶ 6
 * (pause / step / free-run). It always binds 127.0.0.1. Contract version is
7
 * RETRO_CONTROL_CONTRACT (below); the server advertises the highest level whose
8
 * backend callbacks are all present.
961b3c61 Andy McCall 9
 *
10
 * Integration is three calls in the emulator's main loop:
11
 *   retro_control_service();                 // once per iteration (cheap)
12
 *   if (!retro_control_running()) { idle; }  // gate advancing the machine
13
 *   ... run one instruction / frame ...
14
 *   if (frame_completed) retro_control_on_frame();
15
 *
16
 * Threading: the server runs on its own thread but never touches machine state
17
 * there. State-reading endpoints (/mem, /regs, /screenshot) are executed inside
18
 * retro_control_service() ON THE EMULATOR THREAD, so every read is consistent.
19
 * The backend callbacks are therefore called from the emulator thread (except
20
 * get_frame_count, which the server may also read for /status — make it a plain
21
 * atomic-ish counter read).
22
 */
23
#ifndef RETRO_CONTROL_H
24
#define RETRO_CONTROL_H
25
26
#include <stdint.h>
27
#include <stddef.h>
28
7c83b415 Andy McCall ↶ 29
#ifdef __cplusplus
30
extern "C" {
31
#endif
32
674b8447 Andy McCall ↶ 33
/* Highest contract the core implements. The server advertises the highest level
8fd33bc5 Andy McCall ↶ 34
 * whose backend callbacks are all present: 0.6.0 needs get_perf + set_perf (on
35
 * top of 0.5); 0.5.0 needs set_pad + get_pad (on top of 0.4); 0.4.0 needs
36
 * set_pointer + get_pointer (on top of 0.3); 0.3.0 needs inject_key +
37
 * write_mem + capture_audio; 0.2.0 needs inject_key; else 0.1.0. Keeps a
38
 * partial backend honest. /status.capabilities lists each group a backend
39
 * serves whatever level it reaches, so a client can ask for one directly. */
40
#define RETRO_CONTROL_CONTRACT "0.6.0"
961b3c61 Andy McCall 41
42
/* Native framebuffer pixel layout; the server swizzles to PPM RGB. */
43
typedef enum {
44
    RETRO_PIX_RGB888 = 0,   /* 3 bytes/px: R,G,B                       */
45
    RETRO_PIX_RGBA8888,     /* 4 bytes/px: R,G,B,A                     */
46
    RETRO_PIX_BGRA8888      /* 4 bytes/px: B,G,R,A (x16 framebuffer)   */
47
} retro_pix_fmt_t;
48
49
typedef struct {
50
    const uint8_t *pixels;  /* start of the current complete frame     */
51
    int width;
52
    int height;
53
    retro_pix_fmt_t fmt;
54
} retro_framebuffer_t;
55
689a9ccb Andy McCall ↶ 56
/* Key action for inject_key (0.2). */
57
typedef enum {
58
    RETRO_KEY_TAP  = 0,   /* press then release */
59
    RETRO_KEY_DOWN = 1,   /* press / hold       */
60
    RETRO_KEY_UP   = 2    /* release            */
61
} retro_key_action_t;
62
961b3c61 Andy McCall 63
/* The emulator implements these. Any may be NULL: the matching endpoint then
64
 * returns 501 Not Implemented. */
65
typedef struct retro_control_backend {
66
    const char *platform;   /* "x16" | "neo6502" | "agon" | ...        */
67
    const char *emulator;   /* human name, e.g. "x16emu"               */
68
69
    /* Debug-read up to min(len, out_cap) bytes at addr into out, side-effect
70
     * free. bank is the optional bank selector (<0 = current bank). Returns the
71
     * number of bytes written. */
72
    uint32_t (*read_mem)(uint32_t addr, int32_t bank, uint32_t len,
73
                         uint8_t *out, uint32_t out_cap);
74
75
    /* Write a JSON object of registers into buf (NUL-terminated). */
76
    void (*get_regs_json)(char *buf, size_t cap);
77
78
    /* Report the current complete-frame framebuffer. */
79
    void (*get_framebuffer)(retro_framebuffer_t *out);
80
81
    /* Monotonic completed-frame counter. */
82
    uint64_t (*get_frame_count)(void);
689a9ccb Andy McCall ↶ 83
84
    /* 0.2: inject a key. is_text=1 => `value` is a character code point;
85
     * is_text=0 => `value` is a raw platform key code. `action` is a
86
     * retro_key_action_t. Return 1 on success, 0 if unmappable (server -> 400).
87
     * NULL => /key returns 501 and the server advertises contract 0.1.0. */
88
    int (*inject_key)(int is_text, uint32_t value, int action);
89
90
    /* 0.2: soft/cold reset the machine. NULL => /reset returns 501. */
91
    void (*reset)(void);
674b8447 Andy McCall ↶ 92
93
    /* 0.3: write len bytes from `in` at addr (debug/state poke; bank<0 =
94
     * current). Intended for RAM/state — I/O-register writes may trigger device
95
     * side effects. Return bytes written. NULL => POST /mem returns 501. */
96
    uint32_t (*write_mem)(uint32_t addr, int32_t bank, uint32_t len,
97
                          const uint8_t *in);
98
99
    /* 0.3: drain up to `cap` interleaved signed-16-bit audio samples the
100
     * emulator has synthesised since the last call into `out`; set *rate and
101
     * *channels; set *dropped to samples lost to overflow since the last drain.
102
     * Return the number of int16 samples written. NULL => /audio returns 501. */
103
    uint32_t (*capture_audio)(int16_t *out, uint32_t cap,
104
                              int *rate, int *channels, uint32_t *dropped);
346ca3ee Andy McCall ↶ 105
106
    /* 0.4: inject a pointer (mouse) move/click through the platform's native
107
     * pointer path. `absolute` nonzero => (x,y) are absolute pointer
108
     * coordinates (framebuffer/pointer space); zero => (x,y) are relative
109
     * deltas applied to the current position. `buttons` is a bitmask
110
     * (bit0 = primary, bit1 = secondary), or -1 to leave the button state
111
     * unchanged (a pure move). Return 1 on success, 0 if the platform has no
112
     * pointer (server -> 400). NULL => POST /pointer returns 501. */
113
    int (*set_pointer)(int absolute, int32_t x, int32_t y, int buttons);
114
115
    /* 0.4: report the current pointer position and button bitmask (same
116
     * semantics as set_pointer's `buttons`). Return 1 on success (values
117
     * written), 0 if no pointer is available. NULL => GET /pointer returns 501. */
118
    int (*get_pointer)(int32_t *x, int32_t *y, int *buttons);
bed0f1e5 Andy McCall ↶ 119
120
    /* 0.5: present a virtual game controller at `index` and set what is held.
121
     *
122
     * `buttons` is the canonical mask (SPEC "Canonical button layout"): bit0
123
     * LEFT, 1 RIGHT, 2 UP, 3 DOWN, 4 A, 5 B, 6 X, 7 Y, 8 START, 9 SELECT,
124
     * 10 L, 11 R -- the SNES/Neo6502 native order. A backend whose hardware
125
     * disagrees converts here, so a test never learns which machine it is on.
126
     * -1 leaves the mask unchanged.
127
     *
128
     * `connected` is 1 to present the pad, 0 to unplug it, -1 to leave the
129
     * presence unchanged. Unplugging is testable behaviour in its own right:
130
     * a program that asks "is a pad there" before offering two-player needs
131
     * both answers.
132
     *
133
     * A pad is a LEVEL, not an event -- the state persists across frames until
134
     * changed, unlike inject_key's TAP.
135
     *
136
     * MUST drive the same path a real pad takes. On an emulator reading SDL
137
     * joysticks that means presenting a virtual device the existing read path
138
     * picks up, NOT calling into SDL: headless CI has no joystick, which is
139
     * the case this exists for.
140
     *
141
     * Return 1 on success, 0 if the platform has no controller support
142
     * (server -> 400). NULL => POST /pad returns 501. */
143
    int (*set_pad)(int index, int buttons, int connected);
144
145
    /* 0.5: report a pad's presence and held mask (same layout as set_pad).
146
     * Return 1 on success (values written), 0 if unsupported. NULL => GET /pad
147
     * returns 501. */
148
    int (*get_pad)(int index, int *buttons, int *connected);
8fd33bc5 Andy McCall ↶ 149
150
    /* 0.6: the performance metrics, as the JSON body of GET /perf (SPEC
151
     * "GET /perf"). `query` is the request's raw query string ("" for none:
152
     * windows=, zones=, frames=, since=), for the backend to read. Return a
153
     * malloc'd NUL-terminated string the server frees, or NULL on failure
154
     * (server -> 500). Called on the emulator thread, so the metrics are read
155
     * at a frame/instruction boundary like /mem. NULL => GET /perf returns 501
156
     * and the server does not advertise 0.6.0. */
157
    char *(*get_perf)(const char *query);
158
159
    /* 0.6: apply a POST /perf settings body (a JSON object, NUL-terminated;
160
     * SPEC "POST /perf"). Set *status to 200, or 400 for a body the backend
161
     * cannot apply, and return a malloc'd JSON response the server frees (for
162
     * 200, the settings as they now stand; for 400, {"error":...}). NULL
163
     * return => 500. NULL callback => POST /perf returns 501. */
164
    char *(*set_perf)(const char *body, int *status);
961b3c61 Andy McCall 165
} retro_control_backend_t;
166
167
/* Start the server thread bound to 127.0.0.1:port. Non-blocking; 0 on success,
168
 * negative on error. backend must outlive the server. */
169
int  retro_control_start(int port, const retro_control_backend_t *backend);
170
171
/* Drain a pending control request. Call once per main-loop iteration, at an
172
 * instruction/frame boundary. Cheap no-op when idle and when control is off. */
173
void retro_control_service(void);
174
175
/* Nonzero => the machine should advance this iteration. Returns 1 always when
176
 * control is off or the machine is free-running; 0 when paused with no steps
177
 * left. */
178
int  retro_control_running(void);
179
180
/* Call when a video frame completes. Drives /step (decrements the step budget,
181
 * halts and answers when it reaches zero). No-op when control is off. */
182
void retro_control_on_frame(void);
183
184
/* Stop the server (optional; process exit is fine too). */
185
void retro_control_stop(void);
7c83b415 Andy McCall ↶ 186
187
#ifdef __cplusplus
188
}
189
#endif
961b3c61 Andy McCall 190
191
#endif /* RETRO_CONTROL_H */