11/*
22 * Twin - A Tiny Window System
3- * Copyright (c) 2024 National Cheng Kung University, Taiwan
3+ * Copyright (c) 2024-2025 National Cheng Kung University, Taiwan
44 * All rights reserved.
55 */
66
99#include <stdio.h>
1010#include <twin.h>
1111
12+ #ifdef __EMSCRIPTEN__
13+ #include <emscripten.h>
14+ #endif
15+
1216#include "twin_private.h"
1317
1418typedef struct {
@@ -65,6 +69,14 @@ static void _twin_sdl_destroy(twin_screen_t *screen maybe_unused,
6569 SDL_Quit ();
6670}
6771
72+ #ifdef __EMSCRIPTEN__
73+ /* Placeholder main loop to prevent SDL from complaining during initialization.
74+ * This will be replaced by the real main loop in main().
75+ */
76+ static void twin_sdl_placeholder_loop (void ) {}
77+ static bool twin_sdl_placeholder_set = false;
78+ #endif
79+
6880static void twin_sdl_damage (twin_screen_t * screen , twin_sdl_t * tx )
6981{
7082 int width , height ;
@@ -92,7 +104,17 @@ twin_context_t *twin_sdl_init(int width, int height)
92104 return NULL ;
93105 }
94106
95- if (SDL_Init (SDL_INIT_VIDEO ) < 0 ) {
107+ #ifdef __EMSCRIPTEN__
108+ /* Tell SDL we will manage the main loop externally via
109+ * emscripten_set_main_loop, preventing SDL from trying to set up its own
110+ * timing before we are ready.
111+ */
112+ SDL_SetMainReady (); // Prevent SDL from taking over main()
113+ SDL_SetHint (SDL_HINT_EMSCRIPTEN_ASYNCIFY , "0" );
114+ SDL_SetHint (SDL_HINT_EMSCRIPTEN_KEYBOARD_ELEMENT , "#canvas" );
115+ #endif
116+
117+ if (SDL_Init (SDL_INIT_VIDEO | SDL_INIT_EVENTS ) < 0 ) {
96118 log_error ("%s" , SDL_GetError ());
97119 goto bail ;
98120 }
@@ -109,8 +131,23 @@ twin_context_t *twin_sdl_init(int width, int height)
109131 }
110132
111133 tx -> pixels = malloc (width * height * sizeof (* tx -> pixels ));
134+ if (!tx -> pixels ) {
135+ log_error ("Failed to allocate pixel buffer" );
136+ goto bail_window ;
137+ }
112138 memset (tx -> pixels , 255 , width * height * sizeof (* tx -> pixels ));
113139
140+ #ifdef __EMSCRIPTEN__
141+ /* Set up a placeholder main loop to prevent SDL_CreateRenderer from
142+ * complaining about missing main loop. The real main loop will be set
143+ * up in main() after all initialization is complete.
144+ */
145+ if (!twin_sdl_placeholder_set ) {
146+ emscripten_set_main_loop (twin_sdl_placeholder_loop , 0 , 0 );
147+ twin_sdl_placeholder_set = true;
148+ }
149+ #endif
150+
114151 tx -> render = SDL_CreateRenderer (tx -> win , -1 , SDL_RENDERER_ACCELERATED );
115152 if (!tx -> render ) {
116153 log_error ("%s" , SDL_GetError ());
@@ -121,16 +158,30 @@ twin_context_t *twin_sdl_init(int width, int height)
121158
122159 tx -> texture = SDL_CreateTexture (tx -> render , SDL_PIXELFORMAT_ARGB8888 ,
123160 SDL_TEXTUREACCESS_STREAMING , width , height );
161+ if (!tx -> texture ) {
162+ log_error ("%s" , SDL_GetError ());
163+ goto bail_renderer ;
164+ }
124165
125166 ctx -> screen = twin_screen_create (width , height , _twin_sdl_put_begin ,
126167 _twin_sdl_put_span , ctx );
168+ if (!ctx -> screen ) {
169+ log_error ("Failed to create screen" );
170+ goto bail_texture ;
171+ }
127172
128173 twin_set_work (twin_sdl_work , TWIN_WORK_REDISPLAY , ctx );
129174
130175 return ctx ;
131176
177+ bail_texture :
178+ SDL_DestroyTexture (tx -> texture );
179+ bail_renderer :
180+ SDL_DestroyRenderer (tx -> render );
132181bail_pixels :
133182 free (tx -> pixels );
183+ bail_window :
184+ SDL_DestroyWindow (tx -> win );
134185bail :
135186 free (ctx -> priv );
136187 free (ctx );
@@ -150,7 +201,9 @@ static bool twin_sdl_poll(twin_context_t *ctx)
150201 twin_sdl_t * tx = PRIV (ctx );
151202
152203 SDL_Event ev ;
204+ bool has_event = false;
153205 while (SDL_PollEvent (& ev )) {
206+ has_event = true;
154207 twin_event_t tev ;
155208 switch (ev .type ) {
156209 case SDL_WINDOWEVENT :
@@ -188,23 +241,88 @@ static bool twin_sdl_poll(twin_context_t *ctx)
188241 break ;
189242 }
190243 }
244+
245+ /* Yield CPU when idle to avoid busy-waiting.
246+ * Skip delay if events were processed or screen needs update.
247+ */
248+ if (!has_event && !twin_screen_damaged (screen )) {
249+ SDL_Delay (1 ); /* 1ms sleep reduces CPU usage when idle */
250+ }
251+
191252 return true;
192253}
193254
194255static void twin_sdl_exit (twin_context_t * ctx )
195256{
196257 if (!ctx )
197258 return ;
198- free (PRIV (ctx )-> pixels );
259+
260+ twin_sdl_t * tx = PRIV (ctx );
261+
262+ /* Clean up SDL resources */
263+ if (tx -> texture )
264+ SDL_DestroyTexture (tx -> texture );
265+ if (tx -> render )
266+ SDL_DestroyRenderer (tx -> render );
267+ if (tx -> win )
268+ SDL_DestroyWindow (tx -> win );
269+ SDL_Quit ();
270+
271+ /* Free memory */
272+ free (tx -> pixels );
199273 free (ctx -> priv );
200274 free (ctx );
201275}
202276
277+ #ifdef __EMSCRIPTEN__
278+ /* Emscripten main loop state */
279+ static void (* g_wasm_init_callback )(twin_context_t * ) = NULL ;
280+ static bool g_wasm_initialized = false;
281+
282+ /* Main loop callback for Emscripten */
283+ static void twin_sdl_wasm_loop (void * arg )
284+ {
285+ twin_context_t * ctx = (twin_context_t * ) arg ;
286+
287+ /* Perform one-time initialization on first iteration */
288+ if (!g_wasm_initialized && g_wasm_init_callback ) {
289+ g_wasm_init_callback (ctx );
290+ g_wasm_initialized = true;
291+ }
292+
293+ twin_dispatch_once (ctx );
294+ }
295+ #endif
296+
297+ /* Backend start function: unified entry point for both native and WebAssembly
298+ */
299+ static void twin_sdl_start (twin_context_t * ctx ,
300+ void (* init_callback )(twin_context_t * ))
301+ {
302+ #ifdef __EMSCRIPTEN__
303+ /* WebAssembly: Set up Emscripten main loop */
304+ g_wasm_init_callback = init_callback ;
305+ g_wasm_initialized = false;
306+
307+ emscripten_cancel_main_loop (); /* Cancel placeholder from init */
308+ emscripten_set_main_loop_arg (twin_sdl_wasm_loop , ctx , 0 , 1 );
309+ #else
310+ /* Native: Initialize immediately and enter standard dispatch loop */
311+ if (init_callback )
312+ init_callback (ctx );
313+
314+ /* Use twin_dispatch_once() to ensure work queue and timeouts run */
315+ while (twin_dispatch_once (ctx ))
316+ ;
317+ #endif
318+ }
319+
203320/* Register the SDL backend */
204321
205322const twin_backend_t g_twin_backend = {
206323 .init = twin_sdl_init ,
207324 .configure = twin_sdl_configure ,
208325 .poll = twin_sdl_poll ,
326+ .start = twin_sdl_start ,
209327 .exit = twin_sdl_exit ,
210328};
0 commit comments