Overview
The iOS app uses Swift with SwiftUI for the UI and integrates the emulator core as a static library. The core can be built with either the Rust implementation or the CEmu reference backend.Architecture
backend_bridge.c) to select between backends at runtime.
Prerequisites
Building
Step 1: Build the Backend Library
Use the unified build script to compile the Rust core:The build script compiles the emulator backend (Rust or CEmu) as a static library (
.a file) and places it in core/target/<arch>/release/.Step 2: Build and Run in Xcode
After building the backend library:- Select the appropriate scheme:
- Calc-Rust - Uses Rust backend only
- Calc-CEmu - Uses CEmu backend only
- Calc-Both - Includes both backends (runtime switching)
- Select your target device (Simulator or physical device)
- Press ⌘R to build and run
Using Make Shortcuts
Integration Guide
Step 1: Initialize EmulatorBridge
TheEmulatorBridge class provides a Swift-friendly interface to the C API:
ContentView.swift
Step 2: Load ROM and Start Emulation
Loading ROM
Step 3: Run the Emulation Loop
Use Swift’s Task API for the emulation loop:Emulation Loop
Step 4: Render the Display
The emulator provides a framebuffer as a CGImage:Rendering
EmulatorBridge.makeImage() method creates a CGImage directly from the framebuffer:
EmulatorBridge.swift
Step 5: Handle Keyboard Input
The TI-84 Plus CE uses an 8×7 key matrix:Key Input
Key Matrix Reference
Key Matrix Reference
Common key positions:
- ON key:
(2, 0) - ENTER key:
(6, 0) - Arrow keys: Down
(7, 0), Left(7, 1), Right(7, 2), Up(7, 3) - Number keys:
0is(3, 0),1is(3, 1), …,9is(5, 3) - Math:
+is(6, 1),-is(6, 2),×is(6, 3),÷is(6, 4)
source/ios/Calc/Models/KeyDef.swift for the complete key map.Step 6: State Persistence
Save and restore emulator state across app launches:State Management
Step 7: Loading Program Files
Inject.8xp (programs) or .8xv (app variables) into the calculator’s flash:
Program Loading
Backend Switching
The iOS app supports runtime backend switching when multiple backends are linked:Backend Switching
Native Integration (C API)
The Swift bridge calls C functions defined incore/include/emu.h:
C API Functions
emu.h
Swift to C Bridging
TheEmulatorBridge.swift class wraps the C API:
EmulatorBridge.swift
Bridging Header
Add the C header to your bridging header (ios/Calc-Bridging-Header.h):
Bridging Header
Xcode Project Configuration
Linking Backend Libraries
In Xcode, configure the schemes to link the appropriate backend libraries: Calc-Rust scheme:- Links
libemu_rust.afromcore/target/aarch64-apple-ios/release/
- Links
libemu_cemu.afromcore/target/aarch64-apple-ios/release/
- Links both
libemu_rust.aandlibemu_cemu.a - Includes
backend_bridge.cfor runtime switching
Build Settings
- Library Search Paths:
$(PROJECT_DIR)/../core/target/$(RUST_TARGET)/release - Header Search Paths:
$(PROJECT_DIR)/../core/include - Other Linker Flags:
-lemu_rustor-lemu_cemu(or both)
Performance Tuning
Cycle Budget
At 48MHz and 60 FPS, each frame should execute ~800,000 cycles:Performance Tuning
Display Updates
Only update the screen when the LCD is on:Display Check
Background Execution
Pause emulation when the app goes to background:Lifecycle
Example App
The reference implementation is available atsource/ios/:
- ContentView.swift - Main view with state management
- EmulatorView.swift - Emulator display and controls
- EmulatorBridge.swift - Swift wrapper around C API
- StateManager.swift - State persistence to app storage
- KeypadView.swift - On-screen keypad with touch handling
API Reference
EmulatorBridge Methods
Create the emulator instance. Must be called before any other operations.Returns:
true on success, false on failureDestroy the emulator instance and free resources.
Load ROM data into the emulator.Parameters:
data: ROM file contents (typically 4MB for TI-84 Plus CE)
0 on success, negative error code on failureInject a
.8xp or .8xv file into flash. Must be called after loadRom() and before powerOn().Parameters:data: Program or AppVar file contents
Simulate ON key press+release to start execution. Call after
loadRom().Reset the emulator to initial state (cold boot).
Run emulation for the specified number of CPU cycles.Parameters:
cycles: Number of cycles to execute (typically 800,000 per frame)
Create a CGImage from the current framebuffer.Returns: CGImage of the screen (320×240 ARGB), or nil if unavailable
Get direct access to the framebuffer.Returns: Tuple of (pointer to ARGB pixels, width, height)
Set key state in the 8×7 key matrix.Parameters:
row: Key row (0-7)col: Key column (0-7)down:trueif pressed,falseif released
Check if the LCD is on (should display content).Returns:
true if LCD is active, false if off or sleepingGet the backlight brightness level.Returns: 0-255 (0 = off/black)
Save the current emulator state.Returns: State data, or nil on failure
Load a saved emulator state.Parameters:
data: Previously saved state data
0 on success, negative error code on failureStatic Backend Methods
Get the list of available backends.Returns: Array of backend names (e.g.,
["rust", "cemu"])Get the current backend name.Returns: Current backend name, or nil if no backend is loaded
Set the active backend. Must be called before creating an emulator instance.Parameters:
name: Backend name ("rust"or"cemu")
true if successfulCheck if backend switching is available (multiple backends linked).Returns:
true if more than one backend is availableTroubleshooting
Build fails: Library not found
Build fails: Library not found
Ensure you built the backend library first:Then verify the library exists at
core/target/aarch64-apple-ios-sim/release/libemu_rust.aApp crashes on startup with symbol not found
App crashes on startup with symbol not found
Check that the Xcode scheme is linking the correct backend library:
- Calc-Rust: Links
libemu_rust.a - Calc-CEmu: Links
libemu_cemu.a
Emulator runs slowly or stutters
Emulator runs slowly or stutters
- Reduce the cycle count: try 400,000 cycles per frame (0.5x speed)
- Build in Release mode: use the Release scheme in Xcode
- Profile with Instruments (Time Profiler)
Screen stays black after loading ROM
Screen stays black after loading ROM
Call
powerOn() after loading the ROM to simulate pressing the ON key:Cannot switch backends at runtime
Cannot switch backends at runtime
Ensure you’re using the Calc-Both scheme, which includes both backend libraries and the runtime bridge.
Next Steps
Android Integration
Learn how to integrate the emulator into Android apps with Kotlin
Web Integration
Build a web version using WebAssembly