#import "modules/std.sx"; #import "build.sx"; #import "modules/compiler.sx"; #import "modules/opengl.sx"; #import "modules/sdl3.sx"; #import "modules/math"; #import "modules/stb.sx"; #import "modules/stb_truetype.sx"; #import "modules/gpu/api.sx"; #import "modules/gpu/types.sx"; #import "modules/gpu/metal.sx"; #import "modules/ui"; #import "modules/platform/api.sx"; #import "modules/platform/sdl3.sx"; #import "modules/platform/uikit.sx"; #import "board.sx"; #import "board_view.sx"; #run configure_build(); // Fixed seed for the rendered board — the same seed tests/board_init.sx locks // as a snapshot, so the on-screen layout matches that golden gem-for-gem. BOARD_SEED :: 1337; g_plat : Platform = ---; g_pipeline : *UIPipeline = ---; g_viewport_w : f32 = 800.0; g_viewport_h : f32 = 600.0; g_safe_insets : EdgeInsets = .{}; // iOS-only concrete handles kept alongside the boxed `g_plat` so the frame loop // can reach the CAMetalLayer pointer / pixel dims without going through the // protocol box. g_uikit_plat : *UIKitPlatform = null; g_metal_gpu : *MetalGPU = null; // The pure-sx model (board.sx) and its sprites, seeded once in main() and // rendered every frame. Heap-allocated so the view holds stable pointers to // the mutable state across frames. g_board : *Board = null; g_assets : *BoardAssets = null; // Rebuilt each frame inside the pipeline's arena; carries the current safe-area // insets so the grid stays inside the notch / home-indicator region. build_ui :: () -> View { BoardView.{ board = g_board, assets = g_assets, safe = g_safe_insets } } frame :: () { fc := g_plat.begin_frame(); g_viewport_w = fc.viewport_w; g_viewport_h = fc.viewport_h; g_safe_insets = g_plat.safe_insets(); if fc.viewport_w != g_pipeline.screen_width or fc.viewport_h != g_pipeline.screen_height { g_pipeline.resize(fc.viewport_w, fc.viewport_h); } for g_plat.poll_events(): (*ev) { inline if OS != .ios { if ev == { case .key_up: (e) { if e.key == .escape { g_plat.stop(); } } } } g_pipeline.dispatch_event(ev); } inline if OS == .ios { // Lazy-attach Metal once -[SxAppDelegate didFinishLaunching:] has // installed the SxMetalView and its bounds have been measured; both can // lag the first CADisplayLink tick, and a zero-sized drawable aborts // via XPC. if g_uikit_plat.gl_layer == null { return; } if g_uikit_plat.pixel_w <= 0 or g_uikit_plat.pixel_h <= 0 { return; } if g_metal_gpu.layer == null { g_metal_gpu.init(g_uikit_plat.gl_layer, g_uikit_plat.pixel_w, g_uikit_plat.pixel_h); } else if g_metal_gpu.pixel_w != g_uikit_plat.pixel_w or g_metal_gpu.pixel_h != g_uikit_plat.pixel_h { g_metal_gpu.resize(g_uikit_plat.pixel_w, g_uikit_plat.pixel_h); } clear : ClearColor = .{ r = 0.05, g = 0.06, b = 0.10, a = 1.0 }; if !g_metal_gpu.begin_frame(clear) { return; } g_pipeline.tick(); g_metal_gpu.end_frame(fc.target_present_time); } else { glViewport(0, 0, fc.pixel_w, fc.pixel_h); glClearColor(0.05, 0.06, 0.10, 1.0); glClear(GL_COLOR_BUFFER_BIT); g_pipeline.tick(); } g_plat.end_frame(); } main :: () -> void { inline if OS == .ios { u : *UIKitPlatform = xx context.allocator.alloc(size_of(UIKitPlatform)); u.gpu_mode = .metal; if !u.init("m3te", 800, 600) { return; } g_plat = xx u; g_uikit_plat = u; // The CAMetalLayer doesn't exist until didFinishLaunching: runs after we // return into UIApplicationMain, so attach lazily on the first frame. // init(null, 0, 0) only needs the MTLDevice, which is enough for the // texture uploads below. g_metal_gpu = xx context.allocator.alloc(size_of(MetalGPU)); // alloc returns uninitialized memory; struct field defaults are NOT // applied, so List caps/lens would be garbage without this memset. memset(xx g_metal_gpu, 0, size_of(MetalGPU)); if !g_metal_gpu.init(null, 0, 0) { return; } } else { s : *SdlPlatform = xx context.allocator.alloc(size_of(SdlPlatform)); if !s.init("m3te", 800, 600) { return; } g_plat = xx s; } fc := g_plat.begin_frame(); g_viewport_w = fc.viewport_w; g_viewport_h = fc.viewport_h; g_safe_insets = g_plat.safe_insets(); g_pipeline = xx context.allocator.alloc(size_of(UIPipeline)); // Same alloc caveat as above: zero so the optional `gpu` reads as null on // the desktop path (where set_gpu is not called) and the Lists start empty. memset(xx g_pipeline, 0, size_of(UIPipeline)); inline if OS == .ios { g_pipeline.set_gpu(xx g_metal_gpu); } g_pipeline.init(fc.viewport_w, fc.viewport_h); g_pipeline.init_font("assets/fonts/default.ttf", 32.0, fc.dpi_scale); g_board = xx context.allocator.alloc(size_of(Board)); g_board.init(BOARD_SEED); g_assets = xx context.allocator.alloc(size_of(BoardAssets)); g_assets.init(); g_assets.load(g_pipeline.gpu); g_pipeline.set_body(closure(build_ui)); g_plat.run_frame_loop(closure(frame)); g_plat.shutdown(); }