backend-infra-engineer: Post v0.3.9-hotfix7 snapshot (build cleanup)
This commit is contained in:
537
docs/internal/wasm/dev_guide.md
Normal file
537
docs/internal/wasm/dev_guide.md
Normal file
@@ -0,0 +1,537 @@
|
||||
# WASM Development Guide
|
||||
|
||||
**Status:** Active
|
||||
**Last Updated:** 2025-11-25
|
||||
**Purpose:** Technical reference for building, debugging, and deploying WASM builds
|
||||
**Audience:** AI agents and developers working on the YAZE web port
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
1. Emscripten SDK installed and activated:
|
||||
```bash
|
||||
source /path/to/emsdk/emsdk_env.sh
|
||||
```
|
||||
|
||||
2. Verify `emcmake` is available:
|
||||
```bash
|
||||
which emcmake
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
#### Debug Build (Local Development)
|
||||
For debugging memory errors, stack overflows, and async issues:
|
||||
```bash
|
||||
cmake --preset wasm-debug
|
||||
cmake --build build-wasm --parallel
|
||||
```
|
||||
|
||||
**Debug flags enabled:**
|
||||
- `-s SAFE_HEAP=1` - Bounds checking on all memory accesses (shows exact error location)
|
||||
- `-s ASSERTIONS=2` - Verbose runtime assertions
|
||||
- `-g` - Debug symbols for source mapping
|
||||
|
||||
**Output:** `build-wasm/bin/yaze.html`
|
||||
|
||||
#### Release Build (Production)
|
||||
For optimized performance:
|
||||
```bash
|
||||
cmake --preset wasm-release
|
||||
cmake --build build-wasm --parallel
|
||||
```
|
||||
|
||||
**Optimization flags:**
|
||||
- `-O3` - Maximum optimization
|
||||
- `-flto` - Link-time optimization
|
||||
- No debug overhead
|
||||
|
||||
**Output:** `build-wasm/bin/yaze.html`
|
||||
|
||||
### Using the Build Scripts
|
||||
|
||||
#### Full Build and Package
|
||||
```bash
|
||||
./scripts/build-wasm.sh
|
||||
```
|
||||
This will:
|
||||
1. Build the WASM app using `wasm-release` preset
|
||||
2. Package everything into `build-wasm/dist/`
|
||||
3. Copy all web assets (CSS, JS, icons, etc.)
|
||||
|
||||
#### Serve Locally
|
||||
```bash
|
||||
./scripts/serve-wasm.sh [--debug] [port]
|
||||
./scripts/serve-wasm.sh --dist /path/to/dist --port 9000 # custom path (rare)
|
||||
./scripts/serve-wasm.sh --force --port 8080 # reclaim a busy port
|
||||
```
|
||||
Serves from `build-wasm/dist/` on port 8080 by default. The dist reflects the
|
||||
last preset configured in `build-wasm/` (debug or release), so rebuild with the
|
||||
desired preset when switching modes.
|
||||
|
||||
**Important:** Always serve from the `dist/` directory, not `bin/`!
|
||||
|
||||
### Gemini + Antigravity Extension Debugging (Browser)
|
||||
These steps get Gemini (via the Antigravity browser extension) attached to your local WASM build:
|
||||
|
||||
1. Build + serve:
|
||||
```bash
|
||||
./scripts/build-wasm.sh debug # or release
|
||||
./scripts/serve-wasm.sh --force 8080 # serves dist/, frees the port
|
||||
```
|
||||
2. In Antigravity, allow/whitelist `http://127.0.0.1:8080` (or your chosen port) and open that URL.
|
||||
3. Open the Terminal tab (backtick key or bottom panel). Focus is automatic; clicking inside also focuses input.
|
||||
4. Verify hooks from DevTools console:
|
||||
```js
|
||||
window.Module?.calledRun // should be true
|
||||
window.z3edTerminal?.executeCommand('help')
|
||||
toggleCollabConsole() // opens collab pane if needed
|
||||
```
|
||||
5. If input is stolen by global shortcuts, click inside the panel; terminal/collab inputs now stop propagation of shortcuts while focused.
|
||||
6. For a clean slate between sessions: `localStorage.clear(); sessionStorage.clear(); location.reload();`
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Memory Access Out of Bounds
|
||||
**Symptom:** `RuntimeError: memory access out of bounds`
|
||||
|
||||
**Solution:**
|
||||
1. Use `wasm-debug` preset (has `SAFE_HEAP=1`)
|
||||
2. Rebuild and test
|
||||
3. The error will show exact function name and line number
|
||||
4. Fix the bounds check in the code
|
||||
5. Switch back to `wasm-release` for production
|
||||
|
||||
### Stack Overflow
|
||||
**Symptom:** `Aborted(stack overflow (Attempt to set SP to...))`
|
||||
|
||||
**Solution:**
|
||||
- Stack size is set to 32MB in both presets
|
||||
- If still overflowing, increase `-s STACK_SIZE=32MB` to 64MB or higher
|
||||
- Check for deep recursion in ROM loading code
|
||||
|
||||
### Async Operation Failed
|
||||
**Symptom:** `Please compile your program with async support` or `can't start an async op while one is in progress`
|
||||
|
||||
**Solution:**
|
||||
- Both presets have `-s ASYNCIFY=1` enabled
|
||||
- If you see nested async errors, check for:
|
||||
- `emscripten_sleep()` called during another async operation
|
||||
- Multiple `emscripten_async_call()` running simultaneously
|
||||
- Remove `emscripten_sleep(0)` calls if not needed (loading manager already yields)
|
||||
|
||||
### Icons Not Displaying
|
||||
**Symptom:** Material Symbols icons show as boxes or don't appear
|
||||
|
||||
**Solution:**
|
||||
- Check browser console for CORS errors
|
||||
- Verify `icons/` directory is copied to `dist/`
|
||||
- Check network tab to see if Google Fonts is loading
|
||||
- Icons use Material Symbols from CDN - ensure internet connection
|
||||
|
||||
### ROM Loading Fails Silently
|
||||
**Symptom:** ROM file is dropped/selected but nothing happens
|
||||
|
||||
**Solution:**
|
||||
1. Check browser console for errors
|
||||
2. Verify ROM file size is valid (Zelda 3 ROMs are ~1MB)
|
||||
3. Check if `Module.ccall` or `Module._LoadRomFromWeb` exists:
|
||||
```js
|
||||
console.log(typeof Module.ccall);
|
||||
console.log(typeof Module._LoadRomFromWeb);
|
||||
```
|
||||
4. If functions are missing, verify `EXPORTED_FUNCTIONS` in `app.cmake` includes them
|
||||
5. Check `FilesystemManager.ready` is `true` before loading
|
||||
|
||||
### Module Initialization Fails
|
||||
**Symptom:** `createYazeModule is not defined` or similar errors
|
||||
|
||||
**Solution:**
|
||||
- Verify `MODULARIZE=1` and `EXPORT_NAME='createYazeModule'` are in `app.cmake`
|
||||
- Check that `yaze.js` is loaded before `app.js` tries to call `createYazeModule()`
|
||||
- Look for JavaScript errors in console during page load
|
||||
|
||||
### Directory Listing Instead of App
|
||||
**Symptom:** Browser shows file list instead of the app
|
||||
|
||||
**Solution:**
|
||||
- Server must run from `build-wasm/dist/` directory
|
||||
- Use `scripts/serve-wasm.sh` which handles this automatically
|
||||
- Or manually: `cd build-wasm/dist && python3 -m http.server 8080`
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
build-wasm/
|
||||
├── bin/ # Raw build output (yaze.html, yaze.wasm, etc.)
|
||||
└── dist/ # Packaged output for deployment
|
||||
├── index.html # Main entry point (copied from bin/yaze.html)
|
||||
├── yaze.js # WASM loader
|
||||
├── yaze.wasm # Compiled WASM binary
|
||||
├── *.css # Web stylesheets
|
||||
├── *.js # Web JavaScript
|
||||
├── icons/ # PWA icons
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Key Files
|
||||
|
||||
**Build Configuration & Scripts:**
|
||||
- **`CMakePresets.json`** - Build configurations (`wasm-debug`, `wasm-release`)
|
||||
- **`src/app/app.cmake`** - WASM linker flags (EXPORTED_FUNCTIONS, MODULARIZE, etc.)
|
||||
- **`scripts/build-wasm.sh`** - Full build and packaging script
|
||||
- **`scripts/serve-wasm.sh`** - Local development server
|
||||
|
||||
**Web Assets:**
|
||||
- **`src/web/shell.html`** - HTML shell template
|
||||
- **`src/web/app.js`** - Main UI logic, module initialization
|
||||
- **`src/web/core/`** - Core JavaScript functionality (agent automation, control APIs)
|
||||
- **`src/web/components/`** - UI components (terminal, collaboration, etc.)
|
||||
- **`src/web/styles/`** - Stylesheets and theme definitions
|
||||
- **`src/web/pwa/`** - Progressive Web App files (service worker, manifest)
|
||||
- **`src/web/debug/`** - Debug and development utilities
|
||||
|
||||
**C++ Platform Layer:**
|
||||
- **`src/app/platform/wasm/wasm_control_api.cc`** - Control API implementation (JS interop)
|
||||
- **`src/app/platform/wasm/wasm_control_api.h`** - Control API declarations
|
||||
- **`src/app/platform/wasm/wasm_session_bridge.cc`** - Session/collaboration bridge
|
||||
- **`src/app/platform/wasm/wasm_drop_handler.cc`** - File drop handler
|
||||
- **`src/app/platform/wasm/wasm_loading_manager.cc`** - Loading progress UI
|
||||
- **`src/app/platform/wasm/wasm_storage.cc`** - IndexedDB storage with memory-safe error handling
|
||||
- **`src/app/platform/wasm/wasm_error_handler.cc`** - Error handling with callback cleanup
|
||||
|
||||
**GUI Utilities:**
|
||||
- **`src/app/gui/core/popup_id.h`** - Session-aware ImGui popup ID generation
|
||||
|
||||
**CI/CD:**
|
||||
- **`.github/workflows/web-build.yml`** - CI/CD for GitHub Pages
|
||||
|
||||
## CMake WASM Configuration
|
||||
|
||||
The WASM build uses specific Emscripten flags in `src/app/app.cmake`:
|
||||
|
||||
```cmake
|
||||
# Key flags for WASM build
|
||||
-s MODULARIZE=1 # Allows async initialization via createYazeModule()
|
||||
-s EXPORT_NAME='createYazeModule' # Function name for module factory
|
||||
-s EXPORTED_RUNTIME_METHODS='[...]' # Runtime methods available in JS
|
||||
-s EXPORTED_FUNCTIONS='[...]' # C functions callable from JS
|
||||
```
|
||||
|
||||
**Important Exports:**
|
||||
- `_main`, `_SetFileSystemReady`, `_LoadRomFromWeb` - Core functions
|
||||
- `_yazeHandleDroppedFile`, `_yazeHandleDropError` - Drag & drop handlers
|
||||
- `_yazeHandleDragEnter`, `_yazeHandleDragLeave` - Drag state tracking
|
||||
- `_malloc`, `_free` - Memory allocation for JS interop
|
||||
|
||||
**Runtime Methods:**
|
||||
- `ccall`, `cwrap` - Function calling
|
||||
- `stringToUTF8`, `UTF8ToString`, `lengthBytesUTF8` - String conversion
|
||||
- `FS`, `IDBFS` - Filesystem access
|
||||
- `allocateUTF8` - String allocation helper
|
||||
|
||||
## Debugging Tips
|
||||
|
||||
1. **Use Browser DevTools:**
|
||||
- Console tab: WASM errors, async errors
|
||||
- Network tab: Check if WASM files load
|
||||
- Sources tab: Source maps (if `-g` flag used)
|
||||
|
||||
2. **Enable Verbose Logging:**
|
||||
- Check browser console for Emscripten messages
|
||||
- Look for `[symbolize_emscripten.inc]` warnings (can be ignored)
|
||||
|
||||
3. **Test Locally First:**
|
||||
- Always test with `wasm-debug` before deploying
|
||||
- Use `serve-wasm.sh` to ensure correct directory structure
|
||||
|
||||
4. **Memory Issues:**
|
||||
- Use `wasm-debug` preset for precise error locations
|
||||
- Check heap resize messages in console
|
||||
- Verify `INITIAL_MEMORY` is sufficient (64MB default)
|
||||
|
||||
## ImGui ID Conflict Prevention
|
||||
|
||||
When multiple editors are docked together, ImGui popup IDs must be unique to prevent undefined behavior. The `popup_id.h` utility provides session-aware ID generation.
|
||||
|
||||
### Usage
|
||||
|
||||
```cpp
|
||||
#include "app/gui/core/popup_id.h"
|
||||
|
||||
// Generate unique popup ID (default session)
|
||||
std::string id = gui::MakePopupId(gui::EditorNames::kOverworld, "Entrance Editor");
|
||||
ImGui::OpenPopup(id.c_str());
|
||||
|
||||
// Match in BeginPopupModal
|
||||
if (ImGui::BeginPopupModal(
|
||||
gui::MakePopupId(gui::EditorNames::kOverworld, "Entrance Editor").c_str(),
|
||||
nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
// ...
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
// With explicit session ID for multi-session support
|
||||
std::string id = gui::MakePopupId(session_id, "Overworld", "Entrance Editor");
|
||||
```
|
||||
|
||||
### ID Pattern
|
||||
|
||||
Pattern: `s{session_id}.{editor}::{popup_name}`
|
||||
|
||||
Examples:
|
||||
- `s0.Overworld::Entrance Editor`
|
||||
- `s0.Palette::CustomPaletteColorEdit`
|
||||
- `s1.Dungeon::Room Properties`
|
||||
|
||||
### Available Editor Names
|
||||
|
||||
Predefined constants in `gui::EditorNames`:
|
||||
- `kOverworld` - Overworld editor
|
||||
- `kPalette` - Palette editor
|
||||
- `kDungeon` - Dungeon editor
|
||||
- `kGraphics` - Graphics editor
|
||||
- `kSprite` - Sprite editor
|
||||
|
||||
### Why This Matters
|
||||
|
||||
Without unique IDs, clicking "Entrance Editor" popup in one docked window may open/close the popup in a different docked editor, causing confusing behavior. The session+editor prefix guarantees uniqueness.
|
||||
|
||||
## Deployment
|
||||
|
||||
### GitHub Pages
|
||||
The workflow (`.github/workflows/web-build.yml`) automatically:
|
||||
1. Builds using `wasm-release` preset
|
||||
2. Packages to `build-wasm/dist/`
|
||||
3. Deploys to GitHub Pages
|
||||
|
||||
**No manual steps needed** - just push to `master`/`main` branch.
|
||||
|
||||
### Manual Deployment
|
||||
1. Build: `./scripts/build-wasm.sh`
|
||||
2. Upload `build-wasm/dist/` contents to your web server
|
||||
3. Ensure server serves `index.html` as default
|
||||
|
||||
## Performance Notes
|
||||
|
||||
**Debug build (`wasm-debug`):**
|
||||
- 2-5x slower due to SAFE_HEAP
|
||||
- 10-20% slower due to ASSERTIONS
|
||||
- Use only for debugging
|
||||
|
||||
**Release build (`wasm-release`):**
|
||||
- Optimized with `-O3` and `-flto`
|
||||
- No debug overhead
|
||||
- Use for production and performance testing
|
||||
|
||||
## When to Use Each Preset
|
||||
|
||||
**Use `wasm-debug` when:**
|
||||
- Debugging memory access errors
|
||||
- Investigating stack overflows
|
||||
- Testing async operation issues
|
||||
- Need source maps for debugging
|
||||
|
||||
**Use `wasm-release` when:**
|
||||
- Testing performance
|
||||
- Preparing for deployment
|
||||
- CI/CD builds
|
||||
- Production releases
|
||||
|
||||
## Performance Best Practices
|
||||
|
||||
Based on the November 2025 performance audit, follow these guidelines when developing for WASM:
|
||||
|
||||
### JavaScript Performance
|
||||
|
||||
**Event Handling:**
|
||||
- Avoid adding event listeners to both canvas AND document for the same events
|
||||
- Use WeakMap to cache processed event objects and avoid redundant work
|
||||
- Only sanitize/process properties relevant to the specific event type
|
||||
|
||||
**Data Structures:**
|
||||
- Use circular buffers instead of arrays with `shift()` for log/history buffers
|
||||
- `Array.shift()` is O(n) - avoid in high-frequency code paths
|
||||
- Example circular buffer pattern:
|
||||
```javascript
|
||||
var buffer = new Array(maxSize);
|
||||
var index = 0;
|
||||
function add(item) {
|
||||
buffer[index] = item;
|
||||
index = (index + 1) % maxSize;
|
||||
}
|
||||
```
|
||||
|
||||
**Polling/Intervals:**
|
||||
- Always store interval/timeout handles for cleanup
|
||||
- Clear intervals when the feature is no longer needed
|
||||
- Set max retry limits to prevent infinite polling
|
||||
- Use flags (e.g., `window.YAZE_MODULE_READY`) to track initialization state
|
||||
|
||||
### Memory Management
|
||||
|
||||
**Service Worker Caching:**
|
||||
- Implement cache size limits with LRU eviction
|
||||
- Don't cache indefinitely - set `MAX_CACHE_SIZE` constants
|
||||
- Clean up old cache versions on activation
|
||||
|
||||
**C++ Memory in EM_JS:**
|
||||
- Always `free()` allocated memory in error paths, not just success paths
|
||||
- Check if pointers are non-null before freeing
|
||||
- Example pattern:
|
||||
```cpp
|
||||
if (result != 0) {
|
||||
if (data_ptr) free(data_ptr); // Always free on error
|
||||
return absl::InternalError(...);
|
||||
}
|
||||
```
|
||||
|
||||
**Callback Cleanup:**
|
||||
- Add timeout/expiry tracking for stored callbacks
|
||||
- Register cleanup handlers for page unload events
|
||||
- Periodically clean stale entries (e.g., every minute)
|
||||
|
||||
### Race Condition Prevention
|
||||
|
||||
**Module Initialization:**
|
||||
- Use explicit ready flags, not just existence checks
|
||||
- Set ready flag AFTER all initialization is complete
|
||||
- Pattern:
|
||||
```javascript
|
||||
window.YAZE_MODULE_READY = false;
|
||||
createModule().then(function(instance) {
|
||||
window.Module = instance;
|
||||
window.YAZE_MODULE_READY = true; // Set AFTER assignment
|
||||
});
|
||||
```
|
||||
|
||||
**Promise Initialization:**
|
||||
- Create promises synchronously before any async operations
|
||||
- Use synchronous lock patterns to prevent duplicate promises:
|
||||
```javascript
|
||||
if (this.initPromise) return this.initPromise;
|
||||
this.initPromise = new Promise(...); // Immediate assignment
|
||||
// Then do async work
|
||||
```
|
||||
|
||||
**Redundant Operations:**
|
||||
- Use flags to track completed operations
|
||||
- Avoid multiple setTimeout calls for the same operation
|
||||
- Check flags before executing expensive operations
|
||||
|
||||
### File Handling
|
||||
|
||||
**Avoid Double Reading:**
|
||||
- When files are read via FileReader, pass the `Uint8Array` directly
|
||||
- Don't re-read files in downstream handlers
|
||||
- Use methods like `handleRomData(filename, data)` instead of `handleRomUpload(file)`
|
||||
|
||||
### C++ Mutex Best Practices
|
||||
|
||||
**JS Calls and Locks:**
|
||||
- Always call JS functions OUTSIDE mutex locks
|
||||
- JS calls can block/yield - holding a lock during JS calls risks deadlock
|
||||
- Pattern:
|
||||
```cpp
|
||||
std::string data;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
data = operations_[handle]->data; // Copy inside lock
|
||||
}
|
||||
js_function(data.c_str()); // Call outside lock
|
||||
```
|
||||
|
||||
## JavaScript APIs
|
||||
|
||||
The WASM build exposes JavaScript APIs for programmatic control and debugging. These are available after the module initializes.
|
||||
|
||||
### API Documentation
|
||||
|
||||
**For detailed API reference documentation:**
|
||||
- **Control & GUI APIs** - See `docs/internal/wasm-yazeDebug-api-reference.md` for `window.yaze.*` API documentation
|
||||
- `window.yaze.editor` - Query editor state and selection
|
||||
- `window.yaze.data` - Read-only ROM data access
|
||||
- `window.yaze.gui` - GUI element discovery and automation
|
||||
- `window.yaze.control` - Programmatic editor control
|
||||
- **Debug APIs** - See `docs/internal/wasm-yazeDebug-api-reference.md` for `window.yazeDebug.*` API documentation
|
||||
- ROM reading, graphics diagnostics, arena status, emulator state
|
||||
- Palette inspection, timeline analysis
|
||||
- AI-formatted state dumps for Gemini/Antigravity debugging
|
||||
|
||||
### Quick API Check
|
||||
|
||||
To verify APIs are available in the browser console:
|
||||
|
||||
```javascript
|
||||
// Check if module is ready
|
||||
window.yazeDebug.isReady()
|
||||
|
||||
// Get ROM status
|
||||
window.yazeDebug.rom.getStatus()
|
||||
|
||||
// Get formatted state for AI
|
||||
window.yazeDebug.formatForAI()
|
||||
```
|
||||
|
||||
### Gemini/Antigravity Debugging
|
||||
|
||||
For AI-assisted debugging workflows using the Antigravity browser extension, see [`docs/internal/agents/wasm-antigravity-playbook.md`](./wasm-antigravity-playbook.md) for detailed instructions on:
|
||||
- Connecting Gemini to your local WASM build
|
||||
- Using debug APIs with AI agents
|
||||
- Common debugging workflows and examples
|
||||
|
||||
### Dungeon Object Rendering Debugging
|
||||
|
||||
For debugging dungeon object rendering issues (objects appearing at wrong positions, wrong sprites, visual discrepancies), see [`docs/internal/wasm_dungeon_debugging.md`](../wasm_dungeon_debugging.md) Section 12: "Antigravity: Debugging Dungeon Object Rendering Issues".
|
||||
|
||||
**Quick Reference for Antigravity:**
|
||||
|
||||
```javascript
|
||||
// 1. Capture screenshot for visual analysis
|
||||
const result = window.yaze.gui.takeScreenshot();
|
||||
const dataUrl = result.dataUrl;
|
||||
|
||||
// 2. Get room data for comparison
|
||||
const roomData = window.aiTools.getRoomData();
|
||||
const tiles = window.yaze.data.getRoomTiles(roomData.id || 0);
|
||||
|
||||
// 3. Check graphics loading status
|
||||
const arena = window.yazeDebug.arena.getStatus();
|
||||
|
||||
// 4. Full diagnostic dump
|
||||
async function getDiagnostic(roomId) {
|
||||
const data = {
|
||||
room_id: roomId,
|
||||
objects: window.yaze.data.getRoomObjects(roomId),
|
||||
properties: window.yaze.data.getRoomProperties(roomId),
|
||||
arena: window.yazeDebug?.arena?.getStatus(),
|
||||
visible_cards: window.aiTools.getVisibleCards()
|
||||
};
|
||||
await navigator.clipboard.writeText(JSON.stringify(data, null, 2));
|
||||
return data;
|
||||
}
|
||||
```
|
||||
|
||||
**Common Issues:**
|
||||
| Symptom | Check |
|
||||
|---------|-------|
|
||||
| Objects invisible | `window.yazeDebug.arena.getStatus().pending_textures` |
|
||||
| Wrong position | Compare `getRoomObjects()` pixel coords vs visual |
|
||||
| Wrong colors | `Module.getDungeonPaletteEvents()` |
|
||||
| Black squares | Wait for deferred texture loading |
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### Primary WASM Documentation (3 docs total)
|
||||
|
||||
- **This Guide** - Building, debugging, CMake config, performance, ImGui ID conflict prevention
|
||||
- [WASM API Reference](../wasm-yazeDebug-api-reference.md) - Full JavaScript API documentation, Agent Discoverability Infrastructure
|
||||
- [WASM Antigravity Playbook](./wasm-antigravity-playbook.md) - AI agent workflows, Gemini integration, quick start guides
|
||||
|
||||
**Archived:** `archive/wasm-docs-2025/` - Historical WASM docs
|
||||
|
||||
### External Resources
|
||||
|
||||
- [Emscripten Documentation](https://emscripten.org/docs/getting_started/index.html)
|
||||
- [WASM Memory Management](https://emscripten.org/docs/porting/emscripten-runtime-environment.html)
|
||||
- [ASYNCIFY Guide](https://emscripten.org/docs/porting/asyncify.html)
|
||||
Reference in New Issue
Block a user