<repo_name>voidberg/dome<file_sep>/src/modules/plugin.c
internal void
PLUGIN_load(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  const char* name = wrenGetSlotString(vm, 1);
  DOME_Result result = PLUGIN_COLLECTION_add(engine, name);
  if (result != DOME_RESULT_SUCCESS) {
    char buf[256];
    sprintf(buf, "There was a problem initialising plugin: %s", name);
    VM_ABORT(vm, buf);
  }
}

<file_sep>/docs/getting-started.md
[< Back](.)

Getting Started
=================

To get started, make sure you've acquired DOME using the [installation](installation) instructions.

Place the `dome` executable to a directory which will serve as your workspace.

Next, create a new file in your workspace directory named `main.wren`. DOME looks by default for a `main.wren` or `game.egg` file (see [Distributing your Game](guides/distribution)) as the entry point to your game.

Open `main.wren` in your favourite text editor and enter the following:

```
class Main {
  construct new() {}
  init() {}
  update() {}
  draw(alpha) {}
}

var Game = Main.new()
```

Save the file and navigate to your workspace in the terminal. Run `./dome` and you should see a black box appear.
Now you're ready to make your first game!

<file_sep>/src/strings.c
char* strToLower(const char* str) {
  size_t length = strlen(str) + 1;
  char* result = malloc(length);
  for (size_t i = 0; i < length; i++) {
    result[i] = tolower(str[i]);
  }
  return result;
}
<file_sep>/AUTHORS.md
Authors
=======
We'd like to thank the following people for their contributions.

  * <NAME>, aka springogeek [https://github.com/avivbeeri]
  * scholar-mage [https://github.com/scholar-mage]
  * <NAME>, aka frarees [https://github.com/frarees]
  * <NAME>, aka clsource [https://github.com/clsource]
  * <NAME> [https://siddhantrao23.github.io]
  * <NAME> [https://github.com/ChayimFriedman2]
  * <NAME> [https://github.com/benstigsen]
<file_sep>/scripts/setup_static_mac_sdl.sh
#!/bin/bash
source ./scripts/vars.sh
git submodule update --init -- $DIRECTORY

if ! [ -d "$DIRECTORY/$FOLDER" ]; then
  cd $DIRECTORY
  mkdir ${FOLDER} ; cd ${FOLDER}
  ../configure CC=$(sh $DIRECTORY/build-scripts/gcc-fat.sh)
else
  cd $DIRECTORY/${FOLDER}
fi

make
# make install

if [ -f "$DIRECTORY/$FOLDER/build/.libs/libSDL2main.a" ]; then
  cp $DIRECTORY/${FOLDER}/build/.libs/libSDL2main.a $LIB_DIR
fi

cp $DIRECTORY/${FOLDER}/build/.libs/libSDL2.a $LIB_DIR
cp $DIRECTORY/${FOLDER}/build/.libs/libSDL2-2.0.0.dylib $LIB_DIR
cp $DIRECTORY/${FOLDER}/build/.libs/libSDL2.dylib $LIB_DIR
cp $DIRECTORY/${FOLDER}/sdl2-config $LIB_DIR/sdl2-config

cp -r $DIRECTORY/include $INCLUDE_DIR/SDL2
cp -r $DIRECTORY/${FOLDER}/include/SDL_config.h  $INCLUDE_DIR/SDL2/SDL_config.h
<file_sep>/CONTRIBUTING.md
# Contributing to DOME

Thank you for your interest in contributing to the DOME project. It means so much to us.
These are the guidelines you should follow to contribute to DOME. Please use your best judgement.

## Code of Conduct

This project is released with a Contributor [Code of Conduct](https://github.com/domeengine/dome/blob/main/CODE_OF_CONDUCT.md). 
By participating in this project you agree to abide by its terms.

## How can I contribute?

### Reporting a Bug / Suggesting an Enhancement

If you find a bug, or want to suggest a new feature or piece of functionality, simply raise an issue and we will consider it.

### Contributing code changes

The DOME repository is managed using a variation of [Git Flow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow). In order to start working on DOME, fork and clone the repository, and then checkout the `develop` branch. From here, you should create a feature branch for your changes.

### Submitting a Pull Request

Once you finish working on your feature, you should create a pull request, targeting the `develop` branch of the repository.
Your code will be checked for code style, presence of documentation if necessary as well as whether your changes match the project Design Philosophy.
When these things have been checked, your changes will be merged.

## Where can I ask for help?

You can ask for help working on DOME in [our Discord server](https://discord.gg/Py96zeH), or raise an issue asking for help.

## Design Philosophy

DOME is designed and implemented according to a set of core principles:

Minimalism: DOME is a minimal, but all-in-one toolkit. This means that it should only contain things a majority of projects would benefit from. Almost everything is introduced into the core engine with a specific use-case in mind. It's preferred to implement new features as libraries "on-top" of DOME, rather than including everything in the core. The libraries which are frequently moved from project to project make good candidates for new features.

Usage-First: Any new API is designed "usage-first", and then an implementation is built around that. This allows for a more "comfortable" and developer-friendly API, because it's how we would _want_ to use it. Once the API is designed, we do what we can to build an implementation to fit that design, adjusting when technical reasons require it.

## Coding Conventions and Style Guides

Code won't be merged into DOME if it doesn't match the existing code style. Here are some of the more obvious style decisions in the codebase.

### C Style

C-code should conform to the following:

* Variables are named with camelCase.
* Functions are named with camelCase.
* Structs which represent "objects" are usually declared in all capitals.
* Methods associated with structs are named with the format `NAME_methodName()`
* Braces are required for control-flow blocks: `while`, `for` and `if` must be followed by a `{` and `}` to mark out the statements which are predicated on that control flow. This is to prevent copy-paste errors, as well as poor semi-colon placement.

```c
if (condition) { // This brace is on the same line as the ending parenthesis.
  //...
}
```

* A space must be placed after these keywords, and before parenthesis of their conditions.

```c
while (condition) {
  //...
}
```

### Wren

Wren code should conform to the following:

* Classes are named with TitleCase, each word being capitalised, including the first.
* Variables, methods and properties all require camelCase, even when they are intended to be constants (follows Wren convention e.g. Num.pi).
* Constants and variables which do not belong to a class may have the first letter of their identifier capitalised as appropriate. (This is necessary for Wren's scoping rules to work.)

```js
var TheAnswerConstant = 42

var degreeOfSeparation = 6

class GraphVisitor {
  static newProperty { _property }
  static maxTries { 4 }
  static someMethod(withInput) { ... }
}
```

* Spaces around control-flow constructs, in similarity to C-styles.
* Braces around control-flow blocks.

### Directory Structure

This repository follows this directory structure.

```scala
$ tree -L 1 .
.
├── assets
├── docs
├── examples
├── include
├── lib
├── scripts
└── src
```

- `assets`: Resource files for building _DOME_ executable. (Icons).
- `docs`: Documentation, both in _Markdown_ and _html_.
- `examples`: Simple examples. For more elaborate ones please go to the https://github.com/domeengine/dome-examples repository.
- `include`: Libraries and external code required for building _DOME_ (simple c and headers).
- `lib`: Libraries and external code required for building _DOME_ (Git submodules).
- `scripts`: Useful tools and scripts that aids building _DOME_.
- `src`: Main _DOME_ source code.
<file_sep>/examples/plugin/test.c
#include <stddef.h>
// You'll need to include the DOME header
#include "dome.h"

static DOME_API_v0* core;
static WREN_API_v0* wren;

static const char* source =  ""
"foreign class ExternalClass {\n" // Source file for an external module
  "construct init() {} \n"
  "foreign alert(text) \n"
"} \n";

void allocate(WrenVM* vm) {
  size_t CLASS_SIZE = 0; // This should be the size of your object's data
  void* obj = wren->setSlotNewForeign(vm, 0, 0, CLASS_SIZE);
}

void alertMethod(WrenVM* vm) {
  // Fetch the method argument
  const char* text = wren->getSlotString(vm, 1);

  // Retrieve the DOME Context from the VM. This is needed for many things.
  DOME_Context ctx = core->getContext(vm);

  core->log(ctx, "%s\n", text);
}

DOME_EXPORT DOME_Result PLUGIN_onInit(DOME_getAPIFunction DOME_getAPI,
    DOME_Context ctx) {

  // Fetch the latest Core API and save it for later use.
  core = DOME_getAPI(API_DOME, DOME_API_VERSION);

  // DOME also provides a subset of the Wren API for accessing slots
  // in foreign methods.
  wren = DOME_getAPI(API_WREN, WREN_API_VERSION);

  core->log(ctx, "Initialising external module\n");

  // Register a module with it's associated source.
  // Avoid giving the module a common name.
  core->registerModule(ctx, "external", source);

  core->registerClass(ctx, "external", "ExternalClass", allocate, NULL);
  core->registerFn(ctx, "external", "ExternalClass.alert(_)", alertMethod);

  // Returning anything other than SUCCESS here will result in the current fiber
  // aborting. Use this to indicate if your plugin initialised successfully.
  return DOME_RESULT_SUCCESS;
}

DOME_EXPORT DOME_Result PLUGIN_preUpdate(DOME_Context ctx) {
  return DOME_RESULT_SUCCESS;
}

DOME_EXPORT DOME_Result PLUGIN_postUpdate(DOME_Context ctx) {
  return DOME_RESULT_SUCCESS;
}
DOME_EXPORT DOME_Result PLUGIN_preDraw(DOME_Context ctx) {
  return DOME_RESULT_SUCCESS;
}
DOME_EXPORT DOME_Result PLUGIN_postDraw(DOME_Context ctx) {
  return DOME_RESULT_SUCCESS;
}

DOME_EXPORT DOME_Result PLUGIN_onShutdown(DOME_Context ctx) {
  return DOME_RESULT_SUCCESS;
}

<file_sep>/src/modules/dome.c
// This informs the engine we want to stop running, and jumps to the end of the game loop if we have no errors to report.
internal void
PROCESS_exit(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  ASSERT_SLOT_TYPE(vm, 1, NUM, "code");
  engine->running = false;
  engine->exit_status = floor(wrenGetSlotDouble(vm, 1));
  wrenSetSlotNull(vm, 0);
}

internal void
PROCESS_getArguments(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  size_t count = engine->argc;
  char** arguments = engine->argv;
  wrenEnsureSlots(vm, 2);
  wrenSetSlotNewList(vm, 0);
  for (size_t i = 0; i < count; i++) {
    wrenSetSlotString(vm, 1, arguments[i]);
    wrenInsertInList(vm, 0, i, 1);
  }
}

internal void
STRING_UTILS_toLowercase(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, STRING, "string");
  int length;
  const char* str = wrenGetSlotBytes(vm, 1, &length);
  char* dest = calloc(length + 1, sizeof(char));
  utf8ncpy(dest, str, length);
  utf8lwr(dest);
  wrenSetSlotBytes(vm, 0, dest, length);
  free(dest);
}

internal void
WINDOW_resize(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  ASSERT_SLOT_TYPE(vm, 1, NUM, "width");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "height");
  uint32_t width = wrenGetSlotDouble(vm, 1);
  uint32_t height = wrenGetSlotDouble(vm, 2);
  SDL_SetWindowSize(engine->window, width, height);
  // Window may not have resized to the specified value because of
  // desktop restraints, but SDL doesn't check this.
  // We can fetch the final display size from the renderer output.
  int32_t newWidth, newHeight;
  SDL_GetRendererOutputSize(engine->renderer, &newWidth, &newHeight);
  SDL_SetWindowSize(engine->window, newWidth, newHeight);
}

internal void
WINDOW_getWidth(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int width = 0;
  SDL_GetWindowSize(engine->window, &width, NULL);
  wrenSetSlotDouble(vm, 0, width);
}

internal void
WINDOW_getHeight(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int height = 0;
  SDL_GetWindowSize(engine->window, NULL, &height);
  wrenSetSlotDouble(vm, 0, height);
}

internal void
WINDOW_setTitle(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  ASSERT_SLOT_TYPE(vm, 1, STRING, "title");
  const char* title = wrenGetSlotString(vm, 1);
  SDL_SetWindowTitle(engine->window, title);
}

internal void
WINDOW_getTitle(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  wrenSetSlotString(vm, 0, SDL_GetWindowTitle(engine->window));
}

internal void
WINDOW_setVsync(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  ASSERT_SLOT_TYPE(vm, 1, BOOL, "vsync");
  bool value = wrenGetSlotBool(vm, 1);
  ENGINE_setupRenderer(engine, value);
}

internal void
WINDOW_setLockStep(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  engine->lockstep = wrenGetSlotBool(vm, 1);
}

internal void
WINDOW_setFullscreen(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  ASSERT_SLOT_TYPE(vm, 1, BOOL, "fullscreen");
  bool value = wrenGetSlotBool(vm, 1);
  SDL_SetWindowFullscreen(engine->window, value ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
}

internal void
WINDOW_getFullscreen(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  uint32_t flags = SDL_GetWindowFlags(engine->window);
  wrenSetSlotBool(vm, 0, (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0);
}

internal void
WINDOW_getFps(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  ENGINE_DEBUG* debug = &engine->debug;
  // Choose alpha depending on how fast or slow you want old averages to decay.
  // 0.9 is usually a good choice.
  double framesThisSecond = 1000.0 / (debug->elapsed+1);
  double alpha = debug->alpha;
  debug->avgFps = alpha * debug->avgFps + (1.0 - alpha) * framesThisSecond;
  wrenSetSlotDouble(vm, 0, debug->avgFps);
}

internal void
WINDOW_setColor(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  ASSERT_SLOT_TYPE(vm, 1, NUM, "color");
  uint32_t color = (uint32_t)wrenGetSlotDouble(vm, 1);
  uint8_t r, g, b;
  getColorComponents(color, &r, &g, &b);
  SDL_SetRenderDrawColor(engine->renderer, r, g, b, 0xFF);
}

internal void
WINDOW_getColor(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  uint8_t r, g, b, a;
  SDL_GetRenderDrawColor(engine->renderer, &r, &g, &b, &a);
  uint32_t color = (b << 16) | (g << 8) | r;
  wrenSetSlotDouble(vm, 0, color);
}

internal void
VERSION_getString(WrenVM* vm) {
  size_t len = 0;
  char* version = DOME_VERSION;
  if (version[len] == 'v') {
    version++;
  }
  for (len = 0; len < strlen(version); len++) {
    if (version[len] != '.' && !isdigit(version[len])) {
      break;
    }
  }
  wrenSetSlotBytes(vm, 0, version, len);
}


<file_sep>/scripts/vars.sh
VERSION=2.0.12
FOLDER=build
DOME_DIR=$PWD
INCLUDE_DIR=$DOME_DIR/include
LIB_DIR=$DOME_DIR/lib
DIRECTORY=$LIB_DIR/SDL
<file_sep>/docs/installation.md
[< Back](.)

Installation
=================

You can either download the compiled binaries from GitHub, or you can build the DOME executable from source.

## Method 1: Download

You can download the latest public version of DOME from [itch.io](https://avivbeeri.itch.io/dome) or [GitHub](https://github.com/domeengine/dome/releases/latest). Pick the correct version for your computer's operating system and architecture.

Once the download is complete, unzip the downloaded archive and place its contents in the directory you want to make your game in.

At this point, you should have an executable named `dome` in your current directory. Test your installation by opening a terminal/command-prompt, navigating to the directory and executing:

```
> ./dome -v
```
It should print out its version and SDL versions.

You can also download the example `game.egg` from the GitHub repository to test dome. Place it in the same folder as your executable and then run dome, and it should begin playing.

## Method 2: Install using Homebrew
If you have [Homebrew](https://brew.sh/) installed (Mac OS X, [Linux and WSL](https://docs.brew.sh/Homebrew-on-Linux)), you can install DOME using the following commands:

```
> brew tap domeengine/tap
> brew install dome
```
This will install `dome` as a global command for use anywhere.

## Method 3: Build from Source

DOME should build on most unix-like platforms, so long as gcc, git and SDL2 are installed. If these are installed, you can skip to the [Compilation](#compilation) step below.


### Pre-requisite: SDL2

On Mac OS X, you can install SDL2 by using [Homebrew](https://brew.sh) via the command `brew install sdl2`, or installing the official SDL2.framework file from [the SDL2 website](https://www.libsdl.org/download-2.0.php). Finally, you can also compile SDL2 from source and install it "the unix way".

Windows is supported, but it's only been tested using a MinGW-w64 MSYS2 environment. For information on setting this up, follow [this guide.](https://github.com/orlp/dev-on-windows/wiki/Installing-GCC--&-MSYS2). You will need a version of SDL2 which is prepared specifically for MinGW-w64.

To compile on Linux, you need to either build SDL2 from source "the unix way", or install a pre-prepared package appropriate to your distribution.

### Compilation

Once the prerequisites are met, you can clone the DOME repository and build an executable binary by doing the following in your terminal:

```
> git clone https://github.com/domeengine/dome.git
> cd dome
> make
```

At this point, you should have an executable named `dome` in your current directory. Test your installation by running:

```
> ./dome examples/spaceshooter
```

If all is well, you should see the example game:

![Image of Tutorial Shmup](https://domeengine.com/assets/shmup.png)
<file_sep>/README.md
# DOME - Design-Oriented Minimalist Engine

A comfortable framework for game development which melds SDL2 and the [Wren scripting language](http://wren.io), written in C.

![Image of DOME logo](https://domeengine.com/assets/logo200.png)

### For more information on how to use DOME and get started, read the docs [here](https://domeengine.com).

## How to Use

### Download

You can download production-ready binaries from our [Releases page](https://github.com/domeengine/dome/releases/latest). This is the recommended method for distribution and easy development.

### Install via Brew

Alternatively, if you have Homebrew installed (Mac OS X, Linux and WSL), you can install DOME using the following commands:

```bash
> brew tap domeengine/tap
> brew install dome
```

### Build

Finally, if you want to build DOME yourself, to make modifications or other reasons, follow these instruction instead.

Ensure you have the shared SDL2 libraries installed on your system first, then to build, run:

```bash
> make
```

This will create an executable named `./dome` (on Mac OS X and Linux), and `./dome-x32.exe` or `./dome-x64.exe`.

### Run

Run `./dome [gamefile.wren]` to run your game. If your initial file is called `main.wren`, just running `./dome` will execute it. Replace `dome` with your built binary name as necessary.

## Basics

Your game's entry point must contain a `Game` variable which contains at least `init()`, `update()` and `draw(_)` methods.

```wren
import "input" for Keyboard
import "graphics" for Canvas, Color

class Main {
  construct new() {}

  init() {
    _x = 10
    _y = 10
    _w = 5
    _h = 5
  }

  update() {
    if (Keyboard.isKeyDown("left")) {
      _x = _x - 1
    }
    if (Keyboard.isKeyDown("right")) {
      _x = _x+ 1
    }
    if (Keyboard.isKeyDown("up")) {
      _y = _y - 1
    }
    if (Keyboard.isKeyDown("down")) {
      _y = _y + 1
    }
  }

  draw(alpha) {
    Canvas.cls()
    var color = Color.rgb(171, 82, 54)
    Canvas.rectfill(_x, _y, _w, _h, color)
  }
}

var Game = Main.new()
```

## Modules

DOME provides the following modules/methods/classes:

- Graphics
  - Canvas
    - Rect
    - Point
    - Circle
    - Lines
  - Color
  - ImageData
    - Draw sprites loaded from files (png)
- Input
  - Keyboard
  - Mouse
  - Gamepads
- Filesystem
  - File reading and writing
- Audio (stereo and mono OGG and WAV files only)

## TODO

You can follow my progress on implementing DOME on [my twitter](https://twitter.com/avivbeeri/status/1012448692119457798).

- Graphics
  - Triangles
- IO
  - Asynchronous Operations
  - Audio and Graphics also
- Network Access
  - UDP
  - HTTP client (maybe)
- Security sandboxing (maybe)

## Dependencies

DOME currently depends on a few libraries to achieve it's functions.

- Wren (This is built by `make` automatically)
- SDL2 (version 2.0.12 or newer, please install separately when building DOME from source)
- utf8.h
- stb_image
- stb_image_write
- stb_truetype
- stb_vorbis
- microtar
- optparse
- jo_gif
- tinydir
- [ABC_fifo](https://github.com/avivbeeri/abc) (A SPMC threadpool/task dispatching FIFO I wrote for this project)
- mkdirp

Apart from SDL2, all other dependancies are baked in or linked statically. DOME aspires to be both minimalist and cross platform, so it depends on as few external components as possible.

## Acknowledgements

- <NAME> for creating Wren and inspiring me to make games, thanks to [Game Programming Patterns](http://gameprogrammingpatterns.com)
- Special thanks to [lqdev](https://github.com/liquid600pgm) for the fantastic logo!
- <NAME> for the most referenced [resources](https://gafferongames.com/) on Game Loops, Physics and Networking in games
- <NAME> for creating [Handmade Hero](https://hero.handmade.network), an inspiration and educational resource that makes this project possible.
- Built-in font comes from [here](https://github.com/dhepper/font8x8).
- <NAME> for [multiple libraries](https://github.com/nothings/stb)
- rxi for [microtar](https://github.com/rxi/microtar)
- <NAME> for [utf8.h](https://github.com/sheredom/utf8.h)
- <NAME> for [optparse](https://github.com/skeeto/optparse) and [pdjson](https://github.com/skeeto/pdjson)
- cxong for [tinydir](https://github.com/cxong/tinydir)
- <NAME> for [jo_gif](https://www.jonolick.com/home/gif-writer)
- <NAME> for [mkdirp](https://github.com/stephenmathieson/mkdirp.c)


### Example Game Resources

- Example game and graphics are derived from [this](https://ztiromoritz.github.io/pico-8-shooter/) fantastic PICO-8 tutorial.
- Aerith's Piano Theme (res/AerisPiano.ogg) by <NAME> is available under a CC BY-SA 3.0 license: [Link](http://www.tannerhelland.com/68/aeris-theme-piano/)
- Game Over Theme (res/music.wav) by Doppelganger is available under a CC BY-SA 3.0 license: [Link](https://opengameart.org/content/game-over-theme)
- Font "Memory" is provided by <NAME>, and is available on their patreon [here](https://www.patreon.com/posts/free-font-memory-28150678) under a [common sense license](http://www.palmentieri.it/somepx/license.txt).
<file_sep>/docs/Gemfile
source 'https://rubygems.org'

# Gems here
gem 'github-pages', group: :jekyll_plugins
<file_sep>/src/plugin.c
internal char*
PLUGIN_COLLECTION_hookName(DOME_PLUGIN_HOOK hook) {
  switch (hook) {
    case DOME_PLUGIN_HOOK_PRE_UPDATE: return "pre-update";
    case DOME_PLUGIN_HOOK_POST_UPDATE: return "post-update";
    case DOME_PLUGIN_HOOK_PRE_DRAW: return "pre-draw";
    case DOME_PLUGIN_HOOK_POST_DRAW: return "post-draw";
    default: return "unknown";
  }
}

internal DOME_Result
PLUGIN_nullHook(DOME_Context ctx) {
  return DOME_RESULT_SUCCESS;
}

internal void
PLUGIN_COLLECTION_initRecord(PLUGIN_COLLECTION* plugins, size_t index) {
  plugins->active[index] = false;
  plugins->objectHandle[index] = NULL;
  plugins->name[index] = NULL;
  plugins->preUpdateHook[index] = PLUGIN_nullHook;
  plugins->postUpdateHook[index] = PLUGIN_nullHook;
  plugins->preDrawHook[index] = PLUGIN_nullHook;
  plugins->postDrawHook[index] = PLUGIN_nullHook;
}

internal void
PLUGIN_COLLECTION_init(ENGINE* engine) {
  PLUGIN_COLLECTION plugins = engine->plugins;
  plugins.max = 0;
  plugins.count = 0;
  assert(plugins.count <= plugins.max);
  plugins.active = NULL;
  plugins.name = NULL;
  plugins.objectHandle = NULL;
  plugins.preUpdateHook = NULL;
  plugins.postUpdateHook = NULL;
  plugins.preDrawHook = NULL;
  plugins.postDrawHook = NULL;
  for (size_t i = 0; i < plugins.max; i++) {
    PLUGIN_COLLECTION_initRecord(&plugins, i);
  }
  engine->plugins = plugins;
}

internal void
PLUGIN_reportHookError(ENGINE* engine, DOME_PLUGIN_HOOK hook, const char* pluginName) {
  ENGINE_printLog(engine, "DOME cannot continue as the following plugin reported a problem:\n");
  ENGINE_printLog(engine, "Plugin: %s - hook: %s\n", pluginName,  PLUGIN_COLLECTION_hookName(hook));
  ENGINE_printLog(engine, "Aborting.\n");
}

#pragma GCC diagnostic push    //Save actual diagnostics state
#pragma GCC diagnostic ignored "-Wpedantic"    //Disable pedantic
internal inline DOME_Plugin_Hook
acquireHook(void* handle, const char* functionName) {
  return SDL_LoadFunction(handle, functionName);
}

internal inline DOME_Plugin_Init_Hook
acquireInitHook(void* handle, const char* functionName) {
  return SDL_LoadFunction(handle, functionName);
}
#pragma GCC diagnostic pop // Restore diagnostics state


internal void
PLUGIN_COLLECTION_free(ENGINE* engine) {
  PLUGIN_COLLECTION plugins = engine->plugins;
  for (size_t i = 0; i < plugins.count; i++) {
    DOME_Plugin_Hook shutdownHook = acquireHook(plugins.objectHandle[i], "PLUGIN_onShutdown");
    if (shutdownHook != NULL) {
      DOME_Result result = shutdownHook(engine);
      if (result != DOME_RESULT_SUCCESS) {
        PLUGIN_reportHookError(engine, DOME_PLUGIN_HOOK_SHUTDOWN, plugins.name[i]);
      }
    }
    plugins.active[i] = false;

    free((void*)plugins.name[i]);
    plugins.name[i] = NULL;

    SDL_UnloadObject(plugins.objectHandle[i]);
    plugins.objectHandle[i] = NULL;

    plugins.preUpdateHook[i] = NULL;
    plugins.postUpdateHook[i] = NULL;
    plugins.preDrawHook[i] = NULL;
    plugins.postDrawHook[i] = NULL;
  }

  plugins.max = 0;
  plugins.count = 0;

  free((void*)plugins.active);
  plugins.active = NULL;

  free((void*)plugins.name);
  plugins.name = NULL;

  free((void*)plugins.objectHandle);
  plugins.objectHandle = NULL;

  engine->plugins = plugins;

  free((void*)plugins.preUpdateHook);
  free((void*)plugins.postUpdateHook);
  free((void*)plugins.preDrawHook);
  free((void*)plugins.postDrawHook);
}

internal DOME_Result
PLUGIN_COLLECTION_runHook(ENGINE* engine, DOME_PLUGIN_HOOK hook) {
  PLUGIN_COLLECTION plugins = engine->plugins;
  bool failure = false;

  for (size_t i = 0; i < plugins.count; i++) {
    assert(plugins.active[i]);
    DOME_Result result = DOME_RESULT_SUCCESS;

    switch (hook) {
      case DOME_PLUGIN_HOOK_PRE_UPDATE:
        { result = plugins.preUpdateHook[i](engine); } break;
      case DOME_PLUGIN_HOOK_POST_UPDATE:
        { result = plugins.postUpdateHook[i](engine); } break;
      case DOME_PLUGIN_HOOK_PRE_DRAW:
        { result = plugins.preDrawHook[i](engine); } break;
      case DOME_PLUGIN_HOOK_POST_DRAW:
        { result = plugins.postDrawHook[i](engine); } break;
      default: break;
    }
    if (result != DOME_RESULT_SUCCESS) {
      PLUGIN_reportHookError(engine, hook, plugins.name[i]);
      failure = true;
      break;
    }
  }
  if (failure) {
    return DOME_RESULT_FAILURE;
  }

  return DOME_RESULT_SUCCESS;
}

internal DOME_Result
PLUGIN_COLLECTION_add(ENGINE* engine, const char* name) {
  void* handle = SDL_LoadObject(name);
  if (handle == NULL) {
    bool shouldFree = false;
    const char* path = resolvePath(name, &shouldFree);
    handle = SDL_LoadObject(path);
    if (shouldFree) {
      free((void*)path);
    }
  }

  if (handle == NULL) {
    ENGINE_printLog(engine, "%s\n", SDL_GetError());
    return DOME_RESULT_FAILURE;
  }

  PLUGIN_COLLECTION plugins = engine->plugins;
  size_t next = plugins.count;

  if (next >= plugins.max) {
    #define PLUGIN_FIELD_REALLOC(FIELD, TYPE) \
    do {\
      void* prev = plugins.FIELD; \
      plugins.FIELD = realloc(plugins.FIELD, sizeof(TYPE) * plugins.max); \
      if (plugins.FIELD == NULL) { \
        plugins.FIELD = prev; \
        ENGINE_printLog(engine, "There was a problem allocating memory for plugins\n"); \
        return DOME_RESULT_FAILURE; \
      }\
    } while(false);

    plugins.max = plugins.max == 0 ? 2 : plugins.max * 2;

    PLUGIN_FIELD_REALLOC(active, bool);
    PLUGIN_FIELD_REALLOC(name, char*);
    PLUGIN_FIELD_REALLOC(objectHandle, void*);
    PLUGIN_FIELD_REALLOC(preUpdateHook, void*);
    PLUGIN_FIELD_REALLOC(postUpdateHook, void*);
    PLUGIN_FIELD_REALLOC(preDrawHook, void*);
    PLUGIN_FIELD_REALLOC(postDrawHook, void*);

    if (next == 0) {
      PLUGIN_COLLECTION_initRecord(&plugins, 0);
    }
    for (size_t i = next + 1; i < plugins.max; i++) {
      PLUGIN_COLLECTION_initRecord(&plugins, i);
    }

   #undef PLUGIN_FIELD_REALLOC
  }
  plugins.active[next] = true;
  plugins.name[next] = strdup(name);
  plugins.objectHandle[next] = handle;
  plugins.count++;

  // Acquire hook function pointers
  DOME_Plugin_Hook hook;
  hook = acquireHook(handle, "PLUGIN_preUpdate");
  if (hook != NULL) {
    plugins.preUpdateHook[next] = hook;
  }
  hook = acquireHook(handle, "PLUGIN_postUpdate");
  if (hook != NULL) {
    plugins.postUpdateHook[next] = hook;
  }
  hook = acquireHook(handle, "PLUGIN_preDraw");
  if (hook != NULL) {
    plugins.preDrawHook[next] = hook;
  }
  hook = acquireHook(handle, "PLUGIN_postDraw");
  if (hook != NULL) {
    plugins.postDrawHook[next] = hook;
  }

  engine->plugins = plugins;

  DOME_Plugin_Init_Hook initHook;
  initHook = acquireInitHook(handle, "PLUGIN_onInit");
  if (initHook != NULL) {
    return initHook(DOME_getAPI, engine);
  }

  return DOME_RESULT_SUCCESS;
}


internal DOME_Result
DOME_registerModuleImpl(DOME_Context ctx, const char* name, const char* source) {

  ENGINE* engine = (ENGINE*)ctx;
  MAP* moduleMap = &(engine->moduleMap);
  if (MAP_addModule(moduleMap, name, source)) {
    return DOME_RESULT_SUCCESS;
  }
  return DOME_RESULT_FAILURE;
}

internal DOME_Result
DOME_registerFnImpl(DOME_Context ctx, const char* moduleName, const char* signature, DOME_ForeignFn method) {

  ENGINE* engine = (ENGINE*)ctx;
  MAP* moduleMap = &(engine->moduleMap);
  if (MAP_addFunction(moduleMap, moduleName, signature, (WrenForeignMethodFn)method)) {
    return DOME_RESULT_SUCCESS;
  }

  return DOME_RESULT_FAILURE;
}

internal DOME_Result
DOME_registerClassImpl(DOME_Context ctx, const char* moduleName, const char* className, DOME_ForeignFn allocate, DOME_FinalizerFn finalize) {

  // TODO: handle null allocate ptr
  ENGINE* engine = (ENGINE*)ctx;
  MAP* moduleMap = &(engine->moduleMap);
  if (MAP_addClass(moduleMap, moduleName, className, (WrenForeignMethodFn)allocate, (WrenFinalizerFn)finalize)) {
    return DOME_RESULT_SUCCESS;
  }

  return DOME_RESULT_FAILURE;
}

internal void
DOME_lockModuleImpl(DOME_Context ctx, const char* moduleName) {
  ENGINE* engine = (ENGINE*)ctx;
  MAP* moduleMap = &(engine->moduleMap);

  MAP_lockModule(moduleMap, moduleName);
}

internal DOME_Context
DOME_getVMContext(WrenVM* vm) {
  return wrenGetUserData(vm);
}
internal void
DOME_printLog(DOME_Context ctx, const char* text, ...) {
  va_list args;
  va_start(args, text);
  ENGINE_printLogVariadic(ctx, text, args);
  va_end(args);
}

WREN_API_v0 wren_v0 = {
  .getUserData = wrenGetUserData,
  .ensureSlots = wrenEnsureSlots,

  .getSlotType = wrenGetSlotType,
  .getSlotCount = wrenGetSlotCount,
  .abortFiber = wrenAbortFiber,

  .setSlotNull = wrenSetSlotNull,
  .setSlotDouble = wrenSetSlotDouble,
  .setSlotString = wrenSetSlotString,
  .setSlotBytes = wrenSetSlotBytes,
  .setSlotBool = wrenSetSlotBool,
  .setSlotNewForeign = wrenSetSlotNewForeign,
  .setSlotNewList = wrenSetSlotNewList,
  .setSlotNewMap = wrenSetSlotNewMap,

  .getSlotBool = wrenGetSlotBool,
  .getSlotDouble = wrenGetSlotDouble,
  .getSlotString = wrenGetSlotString,
  .getSlotBytes = wrenGetSlotBytes,
  .getSlotForeign = wrenGetSlotForeign,

  .getListCount = wrenGetListCount,
  .getListElement = wrenGetListElement,
  .setListElement = wrenSetListElement,
  .insertInList = wrenInsertInList,

  .getMapCount = wrenGetMapCount,
  .getMapContainsKey = wrenGetMapContainsKey,
  .getMapValue = wrenGetMapValue,
  .setMapValue = wrenSetMapValue,
  .removeMapValue = wrenRemoveMapValue,

  .getVariable = wrenGetVariable,
  .getSlotHandle = wrenGetSlotHandle,
  .setSlotHandle = wrenSetSlotHandle,
  .releaseHandle = wrenReleaseHandle,

  .hasVariable = wrenHasVariable,
  .hasModule = wrenHasModule,
  .call = wrenCall,
  .interpret = wrenInterpret,
};

DOME_API_v0 dome_v0 = {
  .registerModule = DOME_registerModuleImpl,
  .registerFn = DOME_registerFnImpl,
  .registerClass = DOME_registerClassImpl,
  .lockModule = DOME_lockModuleImpl,
  .getContext = DOME_getVMContext,
  .log = DOME_printLog
};

external void*
DOME_getAPI(API_TYPE api, int version) {
  if (api == API_DOME) {
    if (version == 0) {
      return &dome_v0;
    }
  } else if (api == API_WREN) {
    if (version == 0) {
      return &wren_v0;
    }
  } else if (api == API_AUDIO) {
    if (version == 0) {
      return &audio_v0;
    }
  }

  return NULL;
}
<file_sep>/src/modules/input.c
// Inspired by the SDL2 approach to button mapping

global_variable const char* controllerButtonMap[] = {
  "a",
  "b",
  "x",
  "y",
  "back",
  "guide",
  "start",
  "leftstick",
  "rightstick",
  "leftshoulder",
  "rightshoulder",
  "up",
  "down",
  "left",
  "right",
  NULL
};
global_variable bool inputCaptured = false;
global_variable WrenHandle* commitMethod = NULL;
global_variable WrenHandle* gpadAddedMethod = NULL;
global_variable WrenHandle* gpadLookupMethod = NULL;
global_variable WrenHandle* gpadRemoveMethod = NULL;

internal void
INPUT_capture(WrenVM* vm) {
  SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC);

  if (!inputCaptured) {
    wrenGetVariable(vm, "input", "Keyboard", 0);
    keyboardClass = wrenGetSlotHandle(vm, 0);

    wrenGetVariable(vm, "input", "Mouse", 0);
    mouseClass = wrenGetSlotHandle(vm, 0);

    wrenGetVariable(vm, "input", "GamePad", 0);
    gamepadClass  = wrenGetSlotHandle(vm, 0);

    updateInputMethod = wrenMakeCallHandle(vm, "update(_,_)");
    commitMethod = wrenMakeCallHandle(vm, "commit()");
    gpadAddedMethod = wrenMakeCallHandle(vm, "addGamePad(_)");
    gpadLookupMethod = wrenMakeCallHandle(vm, "[_]");
    gpadRemoveMethod = wrenMakeCallHandle(vm, "removeGamePad(_)");
    inputCaptured = true;
  }
}

internal WrenInterpretResult
INPUT_commit(WrenVM* vm) {
  wrenEnsureSlots(vm, 1);
  wrenSetSlotHandle(vm, 0, keyboardClass);
  WrenInterpretResult interpreterResult = wrenCall(vm, commitMethod);
  if (interpreterResult != WREN_RESULT_SUCCESS) {
    return interpreterResult;
  }

  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  if (engine->mouse.relative) {
    SDL_GetRelativeMouseState(&(engine->mouse.x), &(engine->mouse.y));
  } else {
    SDL_GetMouseState(&(engine->mouse.x), &(engine->mouse.y));
  }

  wrenEnsureSlots(vm, 1);
  wrenSetSlotHandle(vm, 0, mouseClass);
  interpreterResult = wrenCall(vm, commitMethod);
  if (interpreterResult != WREN_RESULT_SUCCESS) {
    return interpreterResult;
  }
  wrenEnsureSlots(vm, 1);
  wrenSetSlotHandle(vm, 0, gamepadClass);
  interpreterResult = wrenCall(vm, commitMethod);
  if (interpreterResult != WREN_RESULT_SUCCESS) {
    return interpreterResult;
  }

  return WREN_RESULT_SUCCESS;
}



internal void
INPUT_release(WrenVM* vm) {
  if (inputCaptured) {
    wrenReleaseHandle(vm, keyboardClass);
    wrenReleaseHandle(vm, mouseClass);
    wrenReleaseHandle(vm, gamepadClass);
    wrenReleaseHandle(vm, updateInputMethod);
    wrenReleaseHandle(vm, commitMethod);
    wrenReleaseHandle(vm, gpadAddedMethod);
    wrenReleaseHandle(vm, gpadLookupMethod);
    wrenReleaseHandle(vm, gpadRemoveMethod);
    inputCaptured = false;
  }
}

typedef enum {
  DOME_INPUT_KEYBOARD,
  DOME_INPUT_MOUSE,
  DOME_INPUT_CONTROLLER
} DOME_INPUT_TYPE;

internal WrenInterpretResult
INPUT_update(WrenVM* vm, DOME_INPUT_TYPE type, const char* inputName, bool state) {
  if (inputCaptured) {
    wrenEnsureSlots(vm, 3);
    switch (type) {
      default:
      case DOME_INPUT_KEYBOARD: wrenSetSlotHandle(vm, 0, keyboardClass); break;
      case DOME_INPUT_MOUSE: wrenSetSlotHandle(vm, 0, mouseClass); break;
      // It's assumed the controller object is preloaded.
      case DOME_INPUT_CONTROLLER: break;
    }
    wrenSetSlotString(vm, 1, inputName);
    wrenSetSlotBool(vm, 2, state);
    return wrenCall(vm, updateInputMethod);
  }
  return WREN_RESULT_SUCCESS;
}

internal void MOUSE_getX(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int x = ENGINE_getMouseX(engine);
  wrenSetSlotDouble(vm, 0, x);
}

internal void MOUSE_getY(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int y = ENGINE_getMouseY(engine);
  wrenSetSlotDouble(vm, 0, y);
}

internal void MOUSE_getScrollX(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  wrenSetSlotDouble(vm, 0, engine->mouse.scrollX);
}

internal void MOUSE_getScrollY(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  wrenSetSlotDouble(vm, 0, engine->mouse.scrollY);
}

internal void
MOUSE_setRelative(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, BOOL, "relative");
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  bool relative = wrenGetSlotBool(vm, 1);
  ENGINE_setMouseRelative(engine, relative);
}

internal void
MOUSE_getRelative(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  wrenSetSlotBool(vm, 0, engine->mouse.relative);
}

internal void
MOUSE_setHidden(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, BOOL, "hidden");
  bool hidden = wrenGetSlotBool(vm, 1);
  SDL_ShowCursor(hidden ? SDL_DISABLE : SDL_ENABLE);
}

internal void
MOUSE_getHidden(WrenVM* vm) {
  bool shown = SDL_ShowCursor(SDL_QUERY);
  wrenSetSlotBool(vm, 0, !shown);
}

typedef struct {
  int instanceId;
  SDL_GameController* controller;
  SDL_Haptic* haptics;
} GAMEPAD;

internal void
GAMEPAD_allocate(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, NUM, "joystick id");
  int joystickId = floor(wrenGetSlotDouble(vm, 1));
  GAMEPAD* gamepad = wrenSetSlotNewForeign(vm, 0, 0, sizeof(GAMEPAD));

  if (joystickId == -1 || SDL_IsGameController(joystickId) == SDL_FALSE) {
    gamepad->controller = NULL;
    gamepad->haptics = NULL;
    gamepad->instanceId = -1;
    return;
  }
  SDL_GameController* controller = SDL_GameControllerOpen(joystickId);
  if (controller == NULL) {
    VM_ABORT(vm, "Could not open gamepad");
    return;
  }
  SDL_Joystick* joystick = SDL_GameControllerGetJoystick(controller);
  gamepad->instanceId = SDL_JoystickInstanceID(joystick);
  gamepad->controller = controller;
  gamepad->haptics = SDL_HapticOpenFromJoystick(joystick);
  if (SDL_HapticRumbleSupported(gamepad->haptics) == SDL_FALSE
      || SDL_HapticRumbleInit(gamepad->haptics) != 0) {
    SDL_HapticClose(gamepad->haptics);
    gamepad->haptics = NULL;
  }
}

internal void
closeController(GAMEPAD* gamepad) {
  if (gamepad->haptics != NULL) {
    SDL_HapticClose(gamepad->haptics);
  }

  if (gamepad->controller != NULL) {
    SDL_GameControllerClose(gamepad->controller);
    gamepad->controller = NULL;
  }
}

internal void
GAMEPAD_close(WrenVM* vm) {
  GAMEPAD* gamepad = wrenGetSlotForeign(vm, 0);
  closeController(gamepad);
}

internal void
GAMEPAD_finalize(void* data) {
  closeController((GAMEPAD*)data);
}

internal void
GAMEPAD_rumble(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 0, FOREIGN, "GamePad");
  ASSERT_SLOT_TYPE(vm, 1, NUM, "strength");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "length");
  GAMEPAD* gamepad = wrenGetSlotForeign(vm, 0);
  float strength = fmid(0, wrenGetSlotDouble(vm, 1), 1);
  double length = fmax(0, wrenGetSlotDouble(vm, 2));
  SDL_HapticRumblePlay(gamepad->haptics, strength, length);
}

internal void
GAMEPAD_getAnalogStick(WrenVM* vm) {
  GAMEPAD* gamepad = wrenGetSlotForeign(vm, 0);
  int16_t x = 0;
  int16_t y = 0;
  if (gamepad->controller != NULL) {
    ASSERT_SLOT_TYPE(vm, 1, STRING, "analog stick side");
    const char* side = strToLower(wrenGetSlotString(vm, 1));
    if (STRINGS_EQUAL(side, "left")) {
      x = SDL_GameControllerGetAxis(gamepad->controller, SDL_CONTROLLER_AXIS_LEFTX);
      y = SDL_GameControllerGetAxis(gamepad->controller, SDL_CONTROLLER_AXIS_LEFTY);
    } else if (STRINGS_EQUAL(side, "right")) {
      x = SDL_GameControllerGetAxis(gamepad->controller, SDL_CONTROLLER_AXIS_RIGHTX);
      y = SDL_GameControllerGetAxis(gamepad->controller, SDL_CONTROLLER_AXIS_RIGHTY);
    }
    free((void*)side);
  }

  wrenSetSlotNewList(vm, 0);
  wrenSetSlotDouble(vm, 1, (double)x / SHRT_MAX);
  wrenInsertInList(vm, 0, 0, 1);
  wrenSetSlotDouble(vm, 1, (double)y / SHRT_MAX);
  wrenInsertInList(vm, 0, 1, 1);
}

internal void
GAMEPAD_getTrigger(WrenVM* vm) {
  GAMEPAD* gamepad = wrenGetSlotForeign(vm, 0);
  if (gamepad->controller == NULL) {
    wrenSetSlotDouble(vm, 0, 0.0);
    return;
  }
  ASSERT_SLOT_TYPE(vm, 1, STRING, "trigger side");
  char* side = strToLower(wrenGetSlotString(vm, 1));
  int triggerIndex = SDL_CONTROLLER_AXIS_INVALID;
  if (STRINGS_EQUAL(side, "left")) {
    triggerIndex = SDL_CONTROLLER_AXIS_TRIGGERLEFT;
  } else if (STRINGS_EQUAL(side, "right")) {
    triggerIndex = SDL_CONTROLLER_AXIS_TRIGGERRIGHT;
  }
  free(side);

  int16_t value = SDL_GameControllerGetAxis(gamepad->controller, triggerIndex);
  wrenSetSlotDouble(vm, 0, (double)value / SHRT_MAX);
}

internal void
GAMEPAD_isAttached(WrenVM* vm) {
  GAMEPAD* gamepad = wrenGetSlotForeign(vm, 0);
  if (gamepad->controller == NULL) {
    wrenSetSlotBool(vm, 0, false);
    return;
  }
  wrenSetSlotBool(vm, 0, SDL_GameControllerGetAttached(gamepad->controller) == SDL_TRUE);
}

internal void
GAMEPAD_getName(WrenVM* vm) {
  GAMEPAD* gamepad = wrenGetSlotForeign(vm, 0);
  if (gamepad->controller == NULL) {
    wrenSetSlotString(vm, 0, "NONE");
    return;
  }
  wrenSetSlotString(vm, 0, SDL_GameControllerName(gamepad->controller));
}

internal void
GAMEPAD_getId(WrenVM* vm) {
  GAMEPAD* gamepad = wrenGetSlotForeign(vm, 0);
  if (gamepad->controller == NULL) {
    wrenSetSlotDouble(vm, 0, -1);
    return;
  }
  wrenSetSlotDouble(vm, 0, gamepad->instanceId);
}

internal void
GAMEPAD_getGamePadIds(WrenVM* vm) {
  int maxJoysticks = SDL_NumJoysticks();
  int listCount = 0;
  wrenEnsureSlots(vm, 2);
  wrenSetSlotNewList(vm, 0);
  for(int joystickId = 0; joystickId < maxJoysticks; joystickId++) {
    if (!SDL_IsGameController(joystickId)) {
      continue;
    }
    wrenSetSlotDouble(vm, 1, joystickId);
    wrenInsertInList(vm, 0, listCount, 1);
    listCount++;
  }
}

internal void
GAMEPAD_eventAdded(WrenVM* vm, int joystickId) {
  if (inputCaptured) {
    wrenEnsureSlots(vm, 2);
    wrenSetSlotDouble(vm, 1, joystickId);
    wrenSetSlotHandle(vm, 0, gamepadClass);
    wrenCall(vm, gpadAddedMethod);
  }
}

internal WrenInterpretResult
GAMEPAD_eventButtonPressed(WrenVM* vm, int joystickId, const char* buttonName, bool state) {
  if (inputCaptured) {
    wrenEnsureSlots(vm, 3);
    wrenSetSlotDouble(vm, 1, joystickId);
    wrenSetSlotHandle(vm, 0, gamepadClass);
    WrenInterpretResult result = wrenCall(vm, gpadLookupMethod);
    if (result != WREN_RESULT_SUCCESS) {
      return result;
    }
    // A gamepad instance should be in the 0 slot now
    result = INPUT_update(vm, DOME_INPUT_CONTROLLER, buttonName, state);
    if (result != WREN_RESULT_SUCCESS) {
      return result;
    }
  }

  return WREN_RESULT_SUCCESS;
}

internal void
GAMEPAD_eventRemoved(WrenVM* vm, int instanceId) {
  if (inputCaptured) {
    wrenEnsureSlots(vm, 2);
    wrenSetSlotDouble(vm, 1, instanceId);
    wrenSetSlotHandle(vm, 0, gamepadClass);
    wrenCall(vm, gpadRemoveMethod);
  }
}

internal const char*
GAMEPAD_stringFromButton(SDL_GameControllerButton button) {
  if (button > SDL_CONTROLLER_BUTTON_INVALID && button < SDL_CONTROLLER_BUTTON_MAX) {
    return controllerButtonMap[button];
  }
  return NULL;
}
<file_sep>/include/json/pdjson.h
/*
https://github.com/skeeto/pdjson
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to <http://unlicense.org/>
*/

#ifndef PDJSON_H
#define PDJSON_H

#ifndef PDJSON_SYMEXPORT
#   define PDJSON_SYMEXPORT
#endif

#ifdef __cplusplus
extern "C" {
#else
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
    #include <stdbool.h>
#else
    #ifndef bool
        #define bool int
        #define true 1
        #define false 0
    #endif /* bool */
#endif /* __STDC_VERSION__ */
#endif /* __cplusplus */

#include <stdio.h>

enum json_type {
    JSON_ERROR = 1, JSON_DONE,
    JSON_OBJECT, JSON_OBJECT_END, JSON_ARRAY, JSON_ARRAY_END,
    JSON_STRING, JSON_NUMBER, JSON_TRUE, JSON_FALSE, JSON_NULL
};

struct json_allocator {
    void *(*malloc)(size_t);
    void *(*realloc)(void *, size_t);
    void (*free)(void *);
};

typedef int (*json_user_io)(void *user);

typedef struct json_stream json_stream;
typedef struct json_allocator json_allocator;

PDJSON_SYMEXPORT void json_open_buffer(json_stream *json, const void *buffer, size_t size);
PDJSON_SYMEXPORT void json_open_string(json_stream *json, const char *string);
PDJSON_SYMEXPORT void json_open_stream(json_stream *json, FILE *stream);
PDJSON_SYMEXPORT void json_open_user(json_stream *json, json_user_io get, json_user_io peek, void *user);
PDJSON_SYMEXPORT void json_close(json_stream *json);

PDJSON_SYMEXPORT void json_set_allocator(json_stream *json, json_allocator *a);
PDJSON_SYMEXPORT void json_set_streaming(json_stream *json, bool mode);

PDJSON_SYMEXPORT enum json_type json_next(json_stream *json);
PDJSON_SYMEXPORT enum json_type json_peek(json_stream *json);
PDJSON_SYMEXPORT void json_reset(json_stream *json);
PDJSON_SYMEXPORT const char *json_get_string(json_stream *json, size_t *length);
PDJSON_SYMEXPORT double json_get_number(json_stream *json);

PDJSON_SYMEXPORT enum json_type json_skip(json_stream *json);
PDJSON_SYMEXPORT enum json_type json_skip_until(json_stream *json, enum json_type type);

PDJSON_SYMEXPORT size_t json_get_lineno(json_stream *json);
PDJSON_SYMEXPORT size_t json_get_position(json_stream *json);
PDJSON_SYMEXPORT size_t json_get_depth(json_stream *json);
PDJSON_SYMEXPORT enum json_type json_get_context(json_stream *json, size_t *count);
PDJSON_SYMEXPORT const char *json_get_error(json_stream *json);

PDJSON_SYMEXPORT int json_source_get(json_stream *json);
PDJSON_SYMEXPORT int json_source_peek(json_stream *json);
PDJSON_SYMEXPORT bool json_isspace(int c);

/* internal */

struct json_source {
    int (*get)(struct json_source *);
    int (*peek)(struct json_source *);
    size_t position;
    union {
        struct {
            FILE *stream;
        } stream;
        struct {
            const char *buffer;
            size_t length;
        } buffer;
        struct {
            void *ptr;
            json_user_io get;
            json_user_io peek;
        } user;
    } source;
};

struct json_stream {
    size_t lineno;

    struct json_stack *stack;
    size_t stack_top;
    size_t stack_size;
    enum json_type next;
    unsigned flags;

    struct {
        char *string;
        size_t string_fill;
        size_t string_size;
    } data;

    size_t ntokens;

    struct json_source source;
    struct json_allocator alloc;
    char errmsg[128];
};

#ifdef __cplusplus
} /* extern "C" */
#endif /* __cplusplus */

#endif
<file_sep>/src/modules/graphics.c
internal void
CANVAS_print(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, STRING, "text");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "x");
  ASSERT_SLOT_TYPE(vm, 3, NUM, "y");
  ASSERT_SLOT_TYPE(vm, 4, NUM, "color");
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  char* text = (char*)wrenGetSlotString(vm, 1);
  int64_t x = round(wrenGetSlotDouble(vm, 2));
  int64_t y = round(wrenGetSlotDouble(vm, 3));
  uint32_t c = round(wrenGetSlotDouble(vm, 4));

  ENGINE_print(engine, text, x, y, c);
}

internal void
CANVAS_pget(WrenVM* vm)
{
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y");
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int64_t x = round(wrenGetSlotDouble(vm, 1));
  int64_t y = round(wrenGetSlotDouble(vm, 2));
  uint32_t c = ENGINE_pget(engine, x,y);
  wrenEnsureSlots(vm, 1);
  wrenSetSlotDouble(vm, 0, c);
}
internal void
CANVAS_pset(WrenVM* vm)
{
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y");
  ASSERT_SLOT_TYPE(vm, 3, NUM, "color");
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int64_t x = round(wrenGetSlotDouble(vm, 1));
  int64_t y = round(wrenGetSlotDouble(vm, 2));
  uint32_t c = round(wrenGetSlotDouble(vm, 3));
  ENGINE_pset(engine, x,y,c);
}

internal void
CANVAS_circle_filled(WrenVM* vm)
{
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y");
  ASSERT_SLOT_TYPE(vm, 3, NUM, "radius");
  ASSERT_SLOT_TYPE(vm, 4, NUM, "color");
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int64_t x = round(wrenGetSlotDouble(vm, 1));
  int64_t y = round(wrenGetSlotDouble(vm, 2));
  int64_t r = round(wrenGetSlotDouble(vm, 3));
  if (r < 0) {
    VM_ABORT(vm, "Circle radius must not be negative");
    return;
  }
  uint32_t c = round(wrenGetSlotDouble(vm, 4));
  ENGINE_circle_filled(engine, x, y, r, c);
}

internal void
CANVAS_circle(WrenVM* vm)
{
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y");
  ASSERT_SLOT_TYPE(vm, 3, NUM, "radius");
  ASSERT_SLOT_TYPE(vm, 4, NUM, "color");
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int64_t x = round(wrenGetSlotDouble(vm, 1));
  int64_t y = round(wrenGetSlotDouble(vm, 2));
  int64_t r = round(wrenGetSlotDouble(vm, 3));
  if (r < 0) {
    VM_ABORT(vm, "Circle radius must not be negative");
    return;
  }
  uint32_t c = round(wrenGetSlotDouble(vm, 4));
  ENGINE_circle(engine, x, y, r, c);
}
internal void
CANVAS_line(WrenVM* vm)
{
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x1");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y1");
  ASSERT_SLOT_TYPE(vm, 3, NUM, "x2");
  ASSERT_SLOT_TYPE(vm, 4, NUM, "y2");
  ASSERT_SLOT_TYPE(vm, 5, NUM, "color");
  ASSERT_SLOT_TYPE(vm, 6, NUM, "size");
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int64_t x1 = round(wrenGetSlotDouble(vm, 1));
  int64_t y1 = round(wrenGetSlotDouble(vm, 2));
  int64_t x2 = round(wrenGetSlotDouble(vm, 3));
  int64_t y2 = round(wrenGetSlotDouble(vm, 4));
  uint32_t c = round(wrenGetSlotDouble(vm, 5));
  uint64_t size = round(wrenGetSlotDouble(vm, 6));
  ENGINE_line(engine, x1, y1, x2, y2, c, size);
}

internal void
CANVAS_ellipse(WrenVM* vm)
{
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x1");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y1");
  ASSERT_SLOT_TYPE(vm, 3, NUM, "x2");
  ASSERT_SLOT_TYPE(vm, 4, NUM, "y2");
  ASSERT_SLOT_TYPE(vm, 5, NUM, "color");
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int64_t x1 = round(wrenGetSlotDouble(vm, 1));
  int64_t y1 = round(wrenGetSlotDouble(vm, 2));
  int64_t x2 = round(wrenGetSlotDouble(vm, 3));
  int64_t y2 = round(wrenGetSlotDouble(vm, 4));
  uint32_t c = round(wrenGetSlotDouble(vm, 5));
  ENGINE_ellipse(engine, x1, y1, x2, y2, c);
}

internal void
CANVAS_ellipsefill(WrenVM* vm)
{
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x1");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y1");
  ASSERT_SLOT_TYPE(vm, 3, NUM, "x2");
  ASSERT_SLOT_TYPE(vm, 4, NUM, "y2");
  ASSERT_SLOT_TYPE(vm, 5, NUM, "color");
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int64_t x1 = round(wrenGetSlotDouble(vm, 1));
  int64_t y1 = round(wrenGetSlotDouble(vm, 2));
  int64_t x2 = round(wrenGetSlotDouble(vm, 3));
  int64_t y2 = round(wrenGetSlotDouble(vm, 4));
  uint32_t c = round(wrenGetSlotDouble(vm, 5));
  ENGINE_ellipsefill(engine, x1, y1, x2, y2, c);
}

internal void
CANVAS_rect(WrenVM* vm)
{
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y");
  ASSERT_SLOT_TYPE(vm, 3, NUM, "w");
  ASSERT_SLOT_TYPE(vm, 4, NUM, "h");
  ASSERT_SLOT_TYPE(vm, 5, NUM, "color");
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int64_t x = round(wrenGetSlotDouble(vm, 1));
  int64_t y = round(wrenGetSlotDouble(vm, 2));
  int64_t w = round(wrenGetSlotDouble(vm, 3));
  int64_t h = round(wrenGetSlotDouble(vm, 4));
  if (w < 0) {
    VM_ABORT(vm, "Rectangle width must not be negative");
    return;
  }
  if (h < 0) {
    VM_ABORT(vm, "Rectangle height must not be negative");
    return;
  }
  uint32_t c = round(wrenGetSlotDouble(vm, 5));
  ENGINE_rect(engine, x, y, w, h, c);
}

internal void
CANVAS_rectfill(WrenVM* vm)
{
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y");
  ASSERT_SLOT_TYPE(vm, 3, NUM, "w");
  ASSERT_SLOT_TYPE(vm, 4, NUM, "h");
  ASSERT_SLOT_TYPE(vm, 5, NUM, "color");
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int64_t x = round(wrenGetSlotDouble(vm, 1));
  int64_t y = round(wrenGetSlotDouble(vm, 2));
  int64_t w = round(wrenGetSlotDouble(vm, 3));
  int64_t h = round(wrenGetSlotDouble(vm, 4));
  if (w < 0) {
    VM_ABORT(vm, "Rectangle width must not be negative");
    return;
  }
  if (h < 0) {
    VM_ABORT(vm, "Rectangle height must not be negative");
    return;
  }
  uint32_t c = round(wrenGetSlotDouble(vm, 5));
  ENGINE_rectfill(engine, x, y, w, h, c);
}

internal void
CANVAS_cls(WrenVM* vm)
{
  ASSERT_SLOT_TYPE(vm, 1, NUM, "color");
  uint32_t c = round(wrenGetSlotDouble(vm, 1));
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int64_t offsetX = engine->canvas.offsetX;
  int64_t offsetY = engine->canvas.offsetY;
  // Backgrounds are opaque
  c = c | (0xFF << 24);
  DOME_RECT clip = engine->canvas.clip;
  DOME_RECT rect = {
    .x = 0,
    .y = 0,
    .w = engine->canvas.width,
    .h = engine->canvas.height
  };
  engine->canvas.clip = rect;
  ENGINE_rectfill(engine, -offsetX, -offsetY, engine->canvas.width, engine->canvas.height, c);
  engine->canvas.clip = clip;
}

internal void
CANVAS_getWidth(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  wrenSetSlotDouble(vm, 0, engine->canvas.width);
}
internal void
CANVAS_getHeight(WrenVM* vm) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  wrenSetSlotDouble(vm, 0, engine->canvas.height);
}

internal void
CANVAS_resize(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, NUM, "width");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "height");
  ASSERT_SLOT_TYPE(vm, 3, NUM, "color");
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  uint32_t width = wrenGetSlotDouble(vm, 1);
  uint32_t height = wrenGetSlotDouble(vm, 2);
  uint32_t color = wrenGetSlotDouble(vm, 3);
  bool success = ENGINE_canvasResize(engine, width, height, color);
  if (success == false) {
    VM_ABORT(vm, SDL_GetError());
    return;
  }
}

internal void
CANVAS_clip(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x1");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y1");
  ASSERT_SLOT_TYPE(vm, 3, NUM, "width");
  ASSERT_SLOT_TYPE(vm, 4, NUM, "height");
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  int32_t width = engine->canvas.width;
  int32_t height = engine->canvas.height;
  int64_t x = round(wrenGetSlotDouble(vm, 1));
  int64_t y = round(wrenGetSlotDouble(vm, 2));
  int64_t w = round(wrenGetSlotDouble(vm, 3));
  int64_t h = round(wrenGetSlotDouble(vm, 4));
  DOME_RECT rect = {
    .x = x,
    .y = y,
    .w = min(width, w),
    .h = min(height, h)
  };
  engine->canvas.clip = rect;
}

internal void
CANVAS_offset(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x offset");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y offset");
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  engine->canvas.offsetX = wrenGetSlotDouble(vm, 1);
  engine->canvas.offsetY = wrenGetSlotDouble(vm, 2);
}
<file_sep>/docs/modules/graphics.md
[< Back](.)

graphics
=============

The `graphics` module provides utilities for drawing to the screen.

It contains the following classes:

* [Canvas](#canvas)
* [Color](#color)
* [Drawable](#drawable)
* [Font](#font)
* [ImageData](#imagedata)

## Canvas

The `Canvas` class is the core api for graphical display.

### Fields
#### `static height: Number`
This is the height of the canvas/viewport, in pixels.
#### `static width: Number`
This is the width of the canvas/viewport, in pixels.

### Methods
#### `static circle(x: Number, y: Number, r: Number, c: Color) `
Draw a circle, centered at co-ordinates (_x_, _y_), with a radius _r_, in the color _c_.

#### `static circlefill(x: Number, y: Number, r: Number, c: Color) `
Draw a filled circle, centered at co-ordinates (_x_, _y_), with a radius _r_, in the color _c_.


#### `static clip()`
#### `static clip(x: Number, y: Number, w: Number, h: Number) `
This sets a "clipping region" for the canvas. No pixels will be drawn outside of the defined region. If this method is called without arguments it resets the clipping area to the whole display.

#### `static cls() `
This clears the canvas fully, to black. This ignores the `Canvas.clip` and `Canvas.offset` commands.

#### `static cls(c: Color) `
This clears the canvas fully, to the color _c_. This ignores the `Canvas.clip` and `Canvas.offset` commands.

#### `static draw(object: Drawable, x: Number, y: Number) `
This method is syntactic sugar, to draw objects with a "draw(x: Number, y: Number)" method.

#### `static ellipse(x0: Number, y0: Number, x1: Number, y1: Number, c: Color) `
Draw an ellipse between (_x0, y0_) and (_x1, y1_) in the color _c_.

#### `static ellipsefill(x0: Number, y0: Number, x1: Number, y1: Number, c: Color) `
Draw a filled ellipse between (_x0, y0_) and (_x1, y1_) in the color _c_.

#### `static line(x0: Number, y0: Number, x1: Number, y1: Number, c: Color) `
#### `static line(x0: Number, y0: Number, x1: Number, y1: Number, c: Color, size: Number) `
Draw a line `size` pixels wide between (_x0, y0_) and (_x1, y1_) in the color _c_. By default, `size` is 1.

#### `static offset()`
#### `static offset(x: Number, y: Number) `
Offset all following draw operations by (_x, y_). Calling this without arguments resets the offset to zero. You can use this to implement screen scrolling, or screenshake-style effects.

#### `static print(str, x: Number, y: Number, c: Color) `
Print the text _str_ with the top-left corner at (_x, y_) in color _c_, using the currently set default font. See `Canvas.font` for more information.
The text will be split across multiple lines if a newline (`\n`) character is encountered.
The vertical line spacing defaults to `fontHeight / 4`, where fontHeight is the maximum height of a font character in pixels, but this will be configurable in future versions of DOME.

#### `static print(str, x: Number, y: Number, c: Color, fontName: String) `
Print the text _str_ with the top-left corner at (_x, y_) in color _c_, in the specified font.
The text will be split across multiple lines if a newline (`\n`) character is encountered.
The vertical line spacing is calculated for each font, but this will be configurable in future versions of DOME.

#### `static pset(x: Number, y: Number, c: Color) `
Set the pixel at (_x, y_) to the color _c_.
#### `static pget(x: Number, y: Number): Color `
Get the color of the pixel at (_x, y_).

#### `static rect(x: Number, y: Number, w: Number, h: Number, c: Color) `
Draw a rectangle with the top-left corner at (_x, y_), with a width of _w_ and _h_ in color _c_.

#### `static rectfill(x: Number, y: Number, w: Number, h: Number, c: Color) `
Draw a filled rectangle with the top-left corner at (_x, y_), with a width of _w_ and _h_ in color _c_.

#### `static resize(width: Number, height: Number)`
#### `static resize(width: Number, height: Number, c: Color)`
Resize the canvas to the given `width` and `height`, and reset the color of the canvas to `c`.
If `c` isn't provided, we default to black.
Resizing the canvas resets the "clipping region" to encompass the whole canvas, as if `Canvas.clip()` was called.

### Instance Field
#### `font: String`
This sets the name of the default font used for `Canvas.print(str, x, y, color)`. You can set this to `Font.default` to return to the DOME built-in font.

## Color

An instance of the `Color` class represents a single color which can be used for drawing to the `Canvas`.
DOME comes built-in with the PICO-8 palette, but you can also define and use your own colors in your games.

### Constructors

#### `construct hex(hexcode: String)`
Create a new color with the given hexcode as a string of three to eight alpha-numeric values. Hex values can be upper or lowercase, with or without a `#`. If three or four digits are provided, the number is interpreted as if each digit was written twice (similar to CSS): for example, `#123` is the same as `#112233`. If four or eight digits found, the last digit(s) form the value of the alpha channel. Otherwise, it is defaulted to 255 (fully opaque).
#### `construct hsv(h: Number, s: Number, v: Number)`
Create a new color using the given HSV number and an alpha value of `255`. 
The `s` and `v` parameters must be between `0.0` and `1.0`. 
#### `construct hsv(h: Number, s: Number, v: Number, a: Number)`
Create a new color using the given HSV number and an alpha value of `a`, between `0 - 255`. 
The `s` and `v` parameters must be between `0.0` and `1.0`. 

#### `construct rgb(r: Number, g: Number, b: Number)`
Create a new color with the given RGB values between `0 - 255`, and an alpha value of `255`.

#### `construct rgb(r: Number, g: Number, b: Number, a: Number)`
Create a new color with the given RGBA values between `0 - 255`.

#### `construct new(r: Number, g: Number, b: Number)`
#### `construct new(r: Number, g: Number, b: Number, a: Number)`
Deprecated, aliases for `rgb` constructor.

### Instance Fields

#### `r: Number`
A value between `0 - 255` to represent the red color channel.

#### `g: Number`
A value between `0 - 255` to represent the green color channel.

#### `b: Number`
A value between `0 - 255` to represent the blue color channel.

#### `a: Number`
A value between `0 - 255` to represent the alpha transparency channel.

### Default Palette
The values for the colors in this palette can be found [here](https://www.romanzolotarev.com/pico-8-color-palette/).

 * `static black: Color`
 * `static darkblue : Color`
 * `static darkpurple: Color`
 * `static darkgreen: Color`
 * `static brown: Color`
 * `static darkgray: Color`
 * `static lightgray: Color`
 * `static white: Color`
 * `static red: Color`
 * `static orange: Color`
 * `static yellow: Color`
 * `static green: Color`
 * `static blue: Color`
 * `static indigo: Color`
 * `static pink: Color`
 * `static peach: Color`

In addition to these two values:
 * `static none: Color` - Representing clear transparency.
 * `static purple: Color` - `#8d3cff`, the DOME logo color.

## Drawable
Represents an object which can be drawn to the screen. Objects which conform to this interface can be passed to `Canvas.draw(drawable, x, y)`.
### Instance Methods
#### `draw(x: Number, y: Number): Void`
Draw the image at the given `(x, y)` position on the screen.

## Font

DOME includes a built-in fixed 8x8 pixel font, but you can also load and use fonts from TTF files stored on the file system.

### Static Methods
#### `static load(name: String, path: String, size: Number): Font`
Load the font file at `path`, rasterize it to the pixel height of `size`, and map this to the `name` for later reference. You will need to call this once for each font size, and `name` must be unique, or you will overwrite the old font.

#### `static [fontName]: Font`
You can retrieve a specific font using the index operator, for example `Font["NewFont"]`.

### Instance Methods
#### `print(text: String, x: Number, y: Number, color: Color): Void`
Print the `text` on the canvas at `(x, y)`, in the given `color`.

### Instance Field
#### `antialias: Boolean`
TTF fonts can be scaled to any size, but to look good at certain sizes, you can set antialias to `true` so that some pixels are made partially transparent, to appear smoother. This is `false` by default.

## ImageData
### _extends Drawable_

This class represents the data from an image, such as a sprite or tilemap. 
DOME supports the following formats:
 * JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib)
 * PNG 1/2/4/8/16-bit-per-channel
 * BMP non-1bpp, non-RLE

### Static Methods

#### `static [name]: ImageData`
Fetch a cached image, if it's available. Returns `null` otherwise.

#### `static create(name: String, width: Number, height: Number): ImageData`
Creates a blank image of the size `width x height` and caches it as `name` for future use.

#### `static loadFromFile(path: String): ImageData`
Load an image at the given `path` and cache it for use.

### Instance Fields
#### `height: Number`
#### `width: Number`

### Instance Methods
#### `draw(x: Number, y: Number): Void`
Draw the image at the given `(x, y)` position on the screen.

#### `drawArea(srcX: Number, srcY: Number, srcW: Number, srcH: Number, destX: Number, destY: Number): Void`
Draw a subsection of the image, defined by the rectangle `(srcX, srcY)` to `(srcX + srcW, srcY + srcH)`. The resulting section is placed at `(destX, destY)`.

#### `pset(x: Number, y: Number, color: Color): Void`
Set a pixel at `(x, y)` in the ImageData to color `c`.

#### `pget(x: Number, y: Number): Color`
Fetch the current pixel at `(x, y)` in the ImageData.

#### `saveToFile(path: String): Void`
Saves the current image data at the given `path`.

#### `transform(parameterMap): Drawable`
This returns a `Drawable` which will perform the specified transforms, allowing for more fine-grained control over how images are drawn. You can store the returned drawable and reuse it across frames, while the image is loaded.

Options available are:

 * `srcX`, `srcY` - These specify the top-left corner of the source image region you wish to draw.
 * `srcW`, `srcH` - This is the width and height of the source image region you want to draw.
 * `scaleX`, `scaleY` - You can scale your image in the x and y axis, independant of each other. If either of these are negative, they result in a "flip" operation.
 * `angle` - Rotates the image. This is in degrees, and rounded to the nearest 90 degrees.
 * `opacity` - A number between 0.0 and 1.0, this sets the global opacity of this image. Any alpha values in the image will be multipled by this value,
 * `tint` - A color value which is to be applied on top of the image. Use the tint color's alpha to control how strong the tint is.
 * `mode`, `foreground` and `background` - By default, mode is `"RGBA"`, so your images will draw in their true colors. If you set it to `"MONO"`, any pixels which are black or have transparency will be drawn in the `background` color and all other pixels of the image will be drawn in the `foreground` color. Both colors must be `Color` objects, and default to `Color.black` and `Color.white`, respectively.

Transforms are applied as follows: Crop to the region, then rotate, then scale/flip.

Here is an example:
```wren
spriteSheet.transform({
  "srcX": 8, "srcY": 8,
  "srcW": 8, "srcH": 8,
  "scaleX": 2,
  "scaleY": -2,
  "angle": 90
}).draw(x, y)
```
The code snippet above:
 * crops an 8x8 tile from a spritesheet, starting from (8, 8) in it's image data
 * It then rotates it 90 degrees clockwise
 * Finally, it scales the tile up by 2 in both the X and Y direction, but it flips the tile vertically.

<file_sep>/docs/guides/module-imports.md
[< Back](..)

Importing User Modules
===================

Wren allows scripts to import modules of reusable functionality specific to the embedding environment.

In our case, DOME allows for modules to be imported by path like this:

```
import "[module_path]" for ClassName
```

DOME currently resolves paths in a very simple way: All are relative to the entry point of the game, which is usually `main.wren`. 

Module paths are resolved with the following priority:
* DOME's [built-in modules](../modules)
* Wren VM built-in modules `random` and `meta`.
* User-provided modules at the specified path, relative to the game entry point
  
As an example, imagine this directory structure:
```
.
+-- dome
+-- main.wren
+-- map.wren
+-- objects
    +-- sprite.wren
    +-- background.wren
+-- utils
    +-- math.wren
    +-- vectors.wren
```

To import a class from `utils/math.wren` from `objects/sprite.wren`, you would have an import statement like this:
```
import "./utils/math" for Math
```
This is because even though `sprite.wren` is in the `objects` folder, the path has to be relative to the `main.wren` of the project.


<file_sep>/docs/index.md
Welcome
============

DOME is a framework for making 2D games using the [Wren programming language](http://wren.io) which can be played across platforms.

Get help in the [DOME Discord Server](https://discord.gg/Py96zeH)!

Check out the documentation:

* [Installation](installation)
* [Getting Started](getting-started)
* [FAQ](faq)
* [Modules](modules/)
  * [audio](modules/audio)
  * [dome](modules/dome)
  * [graphics](modules/graphics)
  * [input](modules/input)
  * [io](modules/io)
  * [json](modules/json)
  * [math](modules/math)
  * [plugin](modules/plugin)
  * [random](modules/random)
* Guides
  * [Import Resolution](guides/module-imports)
  * [Game Loop Behaviour](guides/game-loop)
  * [Distributing your game](guides/distribution)
* [Native Plugins](plugins/)
* Examples
  * [DOMEjam](https://itch.io/jam/domejam)
<file_sep>/src/debug.c
#define DEBUG_LOG(string, ...) printf(string"\n",  __VA_ARGS__)

internal char*
DEBUG_printWrenType(WrenType type) {
  switch (type) {
    case WREN_TYPE_BOOL: return "boolean"; break;
    case WREN_TYPE_NUM: return "number"; break;
    case WREN_TYPE_FOREIGN: return "foreign"; break;
    case WREN_TYPE_LIST: return "list"; break;
    case WREN_TYPE_NULL: return "null"; break;
    case WREN_TYPE_STRING: return "string"; break;
    default: return "unknown";
  }
}

// forward declare
internal void ENGINE_printLog(ENGINE* engine, char* line, ...);
#define eprintf(...) ENGINE_printLog(engine, __VA_ARGS__)

internal void DEBUG_printAudioSpec(ENGINE* engine, SDL_AudioSpec spec, AUDIO_TYPE type) {
  if (type == AUDIO_TYPE_WAV) {
    eprintf("WAV ");
  } else if (type == AUDIO_TYPE_OGG) {
    eprintf("OGG ");
  } else {
    eprintf("Unknown audio file detected\n");
  }
  eprintf("Audio: %i Hz ", spec.freq);
  eprintf("%s", spec.channels == 0 ? "Mono" : "Stereo");
  eprintf(" - ");
  if (SDL_AUDIO_ISSIGNED(spec.format)) {
    eprintf("Signed ");
  } else {
    eprintf("Unsigned ");
  }
  eprintf("%i bit (", SDL_AUDIO_BITSIZE(spec.format));
  if (SDL_AUDIO_ISLITTLEENDIAN(spec.format)) {
    eprintf("LSB");
  } else {
    eprintf("MSB");
  }
  eprintf(")\n");
}
#undef eprintf
<file_sep>/scripts/generateEmbedModules.sh
#!/bin/bash
cd src/util

gcc embed.c -o embed -std=gnu99

declare -a arr=(
"stringUtils"
"dome"
"input"
"graphics"
"color"
"font"
"io"
"audio"
"vector"
"image"
"math"
"plugin"
"json"
"platform"
"random"
)
 
declare -a opts=(
)

rm ../modules/modules.inc 2> /dev/null
touch ../modules/modules.inc

rm ../modules/modulemap.c.inc 2> /dev/null
touch ../modules/modulemap.c.inc

for i in "${arr[@]}"
do
  ./embed ../modules/${i}.wren ${i}Module ../modules/${i}.wren.inc
  echo "#include \"${i}.wren.inc\"" >> ../modules/modules.inc
  echo "MAP_addModule(map, \"${i}\", ${i}Module);" >> ../modules/modulemap.c.inc
done

for i in "${opts[@]}"
do
  UPPER=`echo "${i}" | tr '[a-z]' '[A-Z]'`
  ./embed ../modules/${i}.wren ${i}Module ../modules/${i}.wren.inc
  echo "#if DOME_OPT_${UPPER}" >> ../modules/modules.inc
  echo "#include \"${i}.wren.inc\"" >> ../modules/modules.inc
  echo "#endif" >> ../modules/modules.inc
  echo "#if DOME_OPT_${UPPER}" >> ../modules/modulemap.c.inc
  echo "MAP_addModule(map, \"${i}\", ${i}Module);" >> ../modules/modulemap.c.inc
  echo "#endif" >> ../modules/modulemap.c.inc
done
<file_sep>/include/vendor.h
#include <tinydir.h>
#include <utf8.h>
#include <optparse.h>

#include <microtar/microtar.h>
#include <json/pdjson.h>
#include <mkdirp/mkdirp.h>

#define JO_GIF_HEADER_FILE_ONLY
#include <jo_gif.h>

// Set up STB_IMAGE
#define STBI_FAILURE_USERMSG
#define STBI_NO_STDIO
#define STBI_ONLY_JPEG
#define STBI_ONLY_BMP
#define STBI_ONLY_PNG
#include <stb_image.h>

// Setup STB_VORBIS
#define STB_VORBIS_NO_PUSHDATA_API
#define STB_VORBIS_HEADER_ONLY
#include <stb_vorbis.c>

#include <stb_image_write.h>
#include <stb_truetype.h>

#include <ABC_fifo.h>
<file_sep>/scripts/setup_static_linux_sdl.sh
#!/bin/bash
source ./scripts/vars.sh
git submodule update --init -- $DIRECTORY

if ! [ -d "$DIRECTORY/$FOLDER" ]; then
  cd $DIRECTORY
  mkdir ${FOLDER} ; cd ${FOLDER}
  # cmake -DSDL_SHARED=OFF -DSDL_TEST=OFF -DSDL_STATIC=ON -DJACK_SHARED=OFF -DPULSEAUDIO_SHARED=OFF -DALSA_SHARED=OFF -DSNDIO=OFF ..
  ../configure --disable-shared --enable-wayland-shared --enable-x11-shared
else
  cd $DIRECTORY/${FOLDER}
fi

make

# These two are essential
cp $DIRECTORY/${FOLDER}/build/.libs/libSDL2.a $LIB_DIR
cp $DIRECTORY/${FOLDER}/sdl2-config $LIB_DIR/sdl2-config

if [ -f "$DIRECTORY/$FOLDER/build/.libs/libSDL2main.a" ]; then
  cp $DIRECTORY/${FOLDER}/build/.libs/libSDL2main.a $LIB_DIR
fi
if [ -f "$DIRECTORY/$FOLDER/build/.libs/libSDL2main.so" ]; then
  cp $DIRECTORY/${FOLDER}/build/.libs/libSDL2main.so $LIB_DIR
fi

if [ -f "$DIRECTORY/$FOLDER/build/.libs/libSDL2.so" ]; then
cp $DIRECTORY/${FOLDER}/build/.libs/libSDL2.so $LIB_DIR
fi

chmod +x $LIB_DIR/sdl2-config

cp -r $DIRECTORY/include $INCLUDE_DIR/SDL2
cp -r $DIRECTORY/${FOLDER}/include/SDL_config.h  $INCLUDE_DIR/SDL2/SDL_config.h
<file_sep>/src/math.c
/*
 math.c
 */

typedef struct {
  int32_t x;
  int32_t y;
} iVEC;
typedef struct {
  double x;
  double y;
} VEC;

internal double
VEC_len(VEC v) {
  return sqrt(pow(v.x, 2) + pow(v.y, 2));

}

internal VEC
VEC_add(VEC v1, VEC v2) {
  VEC result = { v1.x + v2.x, v1.y + v2.y };
  return result;
}
internal VEC
VEC_sub(VEC v1, VEC v2) {
  VEC result = { v1.x - v2.x, v1.y - v2.y };
  return result;
}
internal VEC
VEC_scale(VEC v, double s) {
  VEC result = { v.x * s, v.y * s };
  return result;
}

internal VEC
VEC_neg(VEC v) {
  return VEC_scale(v, -1);
}

internal double
VEC_dot(VEC v1, VEC v2) {
  return v1.x * v2.x + v1.y * v2.y;
}

internal VEC
VEC_perp(VEC v) {
  VEC result = { -v.y , v.x };
  return result;
}

inline internal float
lerp(float a, float b, float f) {
  return (a * (1.0 - f)) + (b * f);
}

internal int64_t
max(int64_t n1, int64_t n2) {
  if (n1 > n2) {
    return n1;
  }
  return n2;
}

internal int64_t
min(int64_t n1, int64_t n2) {
  if (n1 < n2) {
    return n1;
  }
  return n2;
}

internal double
fmid(double n1, double n2, double n3) {
  double temp;
  if (n1 > n3) {
    temp = n1;
    n1 = n3;
    n3 = temp;
  }
  if (n1 > n2) {
    temp = n1;
    n1 = n2;
    n2 = temp;
  }
  if (n2 < n3) {
    return n2;
  } else {
    return n3;
  }
}

internal int64_t
mid(int64_t n1, int64_t n2, int64_t n3) {
  int64_t temp;
  if (n1 > n3) {
    temp = n1;
    n1 = n3;
    n3 = temp;
  }
  if (n1 > n2) {
    temp = n1;
    n1 = n2;
    n2 = temp;
  }
  if (n2 < n3) {
    return n2;
  } else {
    return n3;
  }
}

internal uint64_t
gcd(uint64_t a, uint64_t b) {
  uint64_t t = b;
  while (b != 0) {
    t = b;
    b = a % b;
    a = t;
  }
  return a;
}
<file_sep>/src/modules/random.c
uint32_t BIT_NOISE1 = 0xB5297A4D;
uint32_t BIT_NOISE2 = 0x68E31DA4;
uint32_t BIT_NOISE3 = 0x1B56C4E9;
uint32_t CAP = 0xFFFFFFFF;

uint32_t squirrel3Hash(uint32_t position, uint32_t seed) {
  uint32_t mangled = position;
  mangled = mangled * BIT_NOISE1;
  mangled = mangled + seed;
  mangled = mangled ^ (mangled >> 8);
  mangled = mangled + BIT_NOISE2;
  mangled = mangled ^ (mangled << 8);
  mangled = mangled * BIT_NOISE3;
  mangled = mangled ^ (mangled >> 8);
  return mangled & CAP;
}

void RANDOM_noise(WrenVM* vm) {
  uint32_t position = wrenGetSlotDouble(vm, 1);
  uint32_t seed = wrenGetSlotDouble(vm, 2);
  wrenSetSlotDouble(vm, 0, squirrel3Hash(position, seed));
}

typedef struct {
  uint32_t seed;
  uint32_t state;
} RANDOM;

void RANDOM_allocate(WrenVM* vm) {
  RANDOM* rng = wrenSetSlotNewForeign(vm, 0, 0, sizeof(RANDOM));
  uint32_t seed = wrenGetSlotDouble(vm, 1);
  rng->seed = seed;
  rng->state = 0;
}

void RANDOM_finalize(void* data) {}

void RANDOM_float(WrenVM* vm) {
  RANDOM* rng = wrenGetSlotForeign(vm, 0);
  double result = squirrel3Hash(rng->state++, rng->seed);
  wrenSetSlotDouble(vm, 0, result / CAP);
}
<file_sep>/docs/modules/plugin.md
[< Back](.)

plugin
================

The `plugin` module is the gateway to loading [native plugins](/plugins/) which can access lower level system features, or bind to other shared libraries.

## Plugin

### Static Methods

#### `static load(name: String): Void`

This will search the current execution context for a plugin of the given `name` and load it. 

The name of the library must be as follows, for different platforms. Assuming your plugin was named `"test"`:
  * On Windows, the file would be `test.dll`
  * On Mac OS X, the file must be named `test.dylib`
  * On Linux and other platforms, the file must be `test.so`

The `name` can be treated as a relative file path from the location of your application entry point, but it is recommended to place plugin library files in the same folder as your `main.wren` or `game.egg` file, for the best compatibility across platforms.

Once the plugin library is loaded, DOME will execute its [Init hook](/plugins/#init), if available. If the hook reports a failure, or the library could not be found, `Plugin.load()` will abort the current fiber.

A plugin is not unloaded until DOME shuts down. There is no way to unload it during execution.
<file_sep>/docs/guides/game-loop.md
[< Back](..)

Game-Loop Behaviour
===================

DOME handles the logistics of operating a game loop, so you only have to worry about what happens during your update loop.

The game loop is broken into three stages:
 * Input and Asynchronous Event Handling
 * Update
 * Draw

The default game-loop can be described as having a "fixed timestep with variable rendering".

This means that the the Update Phase runs with a fixed granularity of 16.6ms but the Input and Draw phases are processed at every opportunity. 
Therefore, you can rely on your game having deterministic behaviour as every update is the same time-slice, and DOME should gracefully handle a variety of machines of differing power. If the draw and input phases take too long, DOME may run your update code multiple times to "catch up".
In addition, DOME can ask your game to render at arbitrary times between ticks. It provides your `Game` with a `draw(alpha)` call, where `alpha`  is a value between 0.0 and 1.0, a percentage of how far between the last tick and the next you are executing. You can use this to extrapolate the game state from the previous tick to "now".

DOME also starts with VSync enabled by default, so it will wait under the VBlank before executing the next frame's game loop.

## Options

There is no way for DOME to detect if it is running on a system which supports VSync, so you can disable it programmatically using the `Window.vsync` setting in the [dome](/modules/dome) module.

It's possible that you spot a single-frame stutter while the game is running at a high framerate. This is due to slight errors in frame time counting, and so DOME catches up unnecessarily. You can disable the catchup behaviour using `Window.lockstep = true`.



<file_sep>/src/main.c
#define _DEFAULT_SOURCE
#define NOMINMAX


#ifndef DOME_VERSION
#define DOME_VERSION "0.0.0 - CUSTOM"
#endif

// Standard libs
#ifdef __MINGW32__
#include <windows.h>
#endif
#include <stdio.h>
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>

#include <unistd.h>
#include <sys/stat.h>
#include <string.h>
#include <libgen.h>
#include <time.h>
#include <math.h>
#ifndef M_PI
  #define M_PI 3.14159265358979323846
#endif

#include <wren.h>
#include <SDL.h>
#include <vendor.h>

// Import plugin-specific definitions
#define WIN_EXPORT
#include "dome.h"
// project-specific definitions
#define external DOME_EXPORT
#define internal DOME_INTERNAL static
#define global_variable static
#define local_persist static


#define INIT_TO_ZERO(Type, name)\
  Type name;\
  memset(&name, 0, sizeof(Type));

#define STRINGS_EQUAL(a, b) (strcmp(a, b) == 0)

#define VM_ABORT(vm, error) do {\
  wrenSetSlotString(vm, 0, error);\
  wrenAbortFiber(vm, 0); \
} while(false);

#define ASSERT_SLOT_TYPE(vm, slot, type, fieldName) \
  if (wrenGetSlotType(vm, slot) != WREN_TYPE_##type) { \
    VM_ABORT(vm, #fieldName " was not " #type); \
    return; \
  }



// Constants
// Screen dimension constants
#define GAME_WIDTH 320
#define GAME_HEIGHT 240
#define SCREEN_WIDTH GAME_WIDTH * 2
#define SCREEN_HEIGHT GAME_HEIGHT * 2

// Used in the io variable, but we need to catch it here
global_variable WrenHandle* bufferClass = NULL;
global_variable WrenHandle* keyboardClass = NULL;
global_variable WrenHandle* mouseClass = NULL;
global_variable WrenHandle* gamepadClass = NULL;
global_variable WrenHandle* updateInputMethod = NULL;

// These are set by cmd arguments
#ifdef DEBUG
global_variable bool DEBUG_MODE = true;
#else
global_variable bool DEBUG_MODE = false;
#endif
global_variable size_t AUDIO_BUFFER_SIZE = 2048;
global_variable size_t GIF_SCALE = 1;



// Game code
#include "math.c"
#include "strings.c"

#include "modules/map.c"

#include "plugin.h"
#include "engine.h"
#include "util/font8x8.h"
#include "io.c"

#include "audio/engine.h"
#include "audio/hashmap.c"
#include "audio/engine.c"
#include "audio/channel.c"
#include "audio/api.c"
#include "debug.c"

#include "engine.c"
#include "plugin.c"

#include "modules/dome.c"
#include "modules/font.c"
#include "modules/io.c"
#include "modules/audio.c"
#include "modules/graphics.c"
#include "modules/image.c"
#include "modules/input.c"
#include "modules/json.c"
#include "modules/platform.c"
#include "modules/random.c"
#include "modules/plugin.c"
#include "util/wrenembed.c"


// Comes last to register modules
#include "vm.c"

typedef struct {
  ENGINE* engine;
  WrenVM* vm;
  WrenHandle* gameClass;
  WrenHandle* updateMethod;
  WrenHandle* drawMethod;
  double MS_PER_FRAME;
  double FPS;
  double lag;
  double elapsed;
  bool windowBlurred;
  uint8_t attempts;
} LOOP_STATE;

internal void
LOOP_release(LOOP_STATE* state) {
  WrenVM* vm = state->vm;

  if (state->drawMethod != NULL) {
    wrenReleaseHandle(vm, state->drawMethod);
  }

  if (state->updateMethod != NULL) {
    wrenReleaseHandle(vm, state->updateMethod);
  }

  if (state->gameClass != NULL) {
    wrenReleaseHandle(vm, state->gameClass);
  }
}

internal int
LOOP_processInput(LOOP_STATE* state) {
  WrenInterpretResult interpreterResult;
  ENGINE* engine = state->engine;
  WrenVM* vm = state->vm;
  engine->mouse.scrollX = 0;
  engine->mouse.scrollY = 0;
  SDL_Event event;
  while(SDL_PollEvent(&event)) {
    switch (event.type)
    {
      case SDL_QUIT:
        engine->running = false;
        break;
      case SDL_WINDOWEVENT:
        {
          if (event.window.event == SDL_WINDOWEVENT_RESIZED ||
              event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
            SDL_RenderGetViewport(engine->renderer, &(engine->viewport));
          } else if (event.window.event == SDL_WINDOWEVENT_FOCUS_LOST) {
            AUDIO_ENGINE_pause(engine->audioEngine);
            state->windowBlurred = true;
          } else if (event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED) {
            AUDIO_ENGINE_resume(engine->audioEngine);
            state->windowBlurred = false;
          }
        } break;
      case SDL_KEYDOWN:
      case SDL_KEYUP:
        {
          SDL_Keycode keyCode = event.key.keysym.sym;
          if (keyCode == SDLK_F3 && event.key.state == SDL_PRESSED && event.key.repeat == 0) {
            engine->debugEnabled = !engine->debugEnabled;
          } else if (keyCode == SDLK_F2 && event.key.state == SDL_PRESSED && event.key.repeat == 0) {
            ENGINE_takeScreenshot(engine);
          } else if (event.key.repeat == 0) {
            char* buttonName = strToLower((char*)SDL_GetKeyName(keyCode));
            interpreterResult = INPUT_update(vm, DOME_INPUT_KEYBOARD, buttonName, event.key.state == SDL_PRESSED);
            free(buttonName);
            if (interpreterResult != WREN_RESULT_SUCCESS) {
              return EXIT_FAILURE;
            }
          }
        } break;
      case SDL_CONTROLLERDEVICEADDED:
        {
          GAMEPAD_eventAdded(vm, event.cdevice.which);
        } break;
      case SDL_CONTROLLERDEVICEREMOVED:
        {
          GAMEPAD_eventRemoved(vm, event.cdevice.which);
        } break;
      case SDL_CONTROLLERBUTTONDOWN:
      case SDL_CONTROLLERBUTTONUP:
        {
          SDL_ControllerButtonEvent cbutton = event.cbutton;
          const char* buttonName = GAMEPAD_stringFromButton(cbutton.button);
          interpreterResult = GAMEPAD_eventButtonPressed(vm, cbutton.which, buttonName, cbutton.state == SDL_PRESSED);
          if (interpreterResult != WREN_RESULT_SUCCESS) {
            return EXIT_FAILURE;
          }
        } break;
      case SDL_MOUSEWHEEL:
        {
          int dir = event.wheel.direction == SDL_MOUSEWHEEL_NORMAL ? 1 : -1;
          engine->mouse.scrollX += event.wheel.x * dir;
          // Down should be positive to match the direction of rendering.
          engine->mouse.scrollY += event.wheel.y * -dir;
        } break;
      case SDL_MOUSEBUTTONDOWN:
      case SDL_MOUSEBUTTONUP:
        {
          char* buttonName;
          switch (event.button.button) {
            case SDL_BUTTON_LEFT: buttonName = "left"; break;
            case SDL_BUTTON_MIDDLE: buttonName = "middle"; break;
            case SDL_BUTTON_RIGHT: buttonName = "right"; break;
            case SDL_BUTTON_X1: buttonName = "x1"; break;
            default:
            case SDL_BUTTON_X2: buttonName = "x2"; break;
          }
          bool state = event.button.state == SDL_PRESSED;
          interpreterResult = INPUT_update(vm, DOME_INPUT_MOUSE, buttonName, state);
          if (interpreterResult != WREN_RESULT_SUCCESS) {
            return EXIT_FAILURE;
          }
        } break;
      case SDL_USEREVENT:
        {
          ENGINE_printLog(engine, "Event code %i\n", event.user.code);
          if (event.user.code == EVENT_LOAD_FILE) {
            FILESYSTEM_loadEventComplete(&event);
          }
        }
    }
  }
  if (inputCaptured) {
    interpreterResult = INPUT_commit(vm);
    if (interpreterResult != WREN_RESULT_SUCCESS) {
      return EXIT_FAILURE;
    }
  }
  return EXIT_SUCCESS;
}

internal int
LOOP_render(LOOP_STATE* state) {
  if (PLUGIN_COLLECTION_runHook(state->engine, DOME_PLUGIN_HOOK_PRE_DRAW) != DOME_RESULT_SUCCESS) {
    return EXIT_FAILURE;
  };
  WrenInterpretResult interpreterResult;
  wrenEnsureSlots(state->vm, 8);
  wrenSetSlotHandle(state->vm, 0, state->gameClass);
  wrenSetSlotDouble(state->vm, 1, ((double)state->lag / state->MS_PER_FRAME));
  interpreterResult = wrenCall(state->vm, state->drawMethod);
  if (interpreterResult != WREN_RESULT_SUCCESS) {
    return EXIT_FAILURE;
  }
  if (PLUGIN_COLLECTION_runHook(state->engine, DOME_PLUGIN_HOOK_POST_DRAW) != DOME_RESULT_SUCCESS) {
    return EXIT_FAILURE;
  };


  return EXIT_SUCCESS;
}

internal void
LOOP_flip(LOOP_STATE* state) {

  state->engine->debug.elapsed = state->elapsed;
  if (state->engine->debugEnabled) {
    ENGINE_drawDebug(state->engine);
  }
  // Flip Buffer to Screen
  SDL_UpdateTexture(state->engine->texture, 0, state->engine->canvas.pixels, state->engine->canvas.width * 4);
  // Flip buffer for recording
  if (state->engine->record.makeGif) {
    size_t imageSize = state->engine->canvas.width * state->engine->canvas.height * 4;
    memcpy(state->engine->record.gifPixels, state->engine->canvas.pixels, imageSize);
  }
  // clear screen
  SDL_RenderClear(state->engine->renderer);
  SDL_RenderCopy(state->engine->renderer, state->engine->texture, NULL, NULL);
  SDL_RenderPresent(state->engine->renderer);
}

internal int
LOOP_update(LOOP_STATE* state) {
  WrenInterpretResult interpreterResult;

  if (PLUGIN_COLLECTION_runHook(state->engine, DOME_PLUGIN_HOOK_PRE_UPDATE) != DOME_RESULT_SUCCESS) {
    return EXIT_FAILURE;
  };

  wrenEnsureSlots(state->vm, 8);
  wrenSetSlotHandle(state->vm, 0, state->gameClass);
  interpreterResult = wrenCall(state->vm, state->updateMethod);
  if (interpreterResult != WREN_RESULT_SUCCESS) {
    return EXIT_FAILURE;
  }
  if (PLUGIN_COLLECTION_runHook(state->engine, DOME_PLUGIN_HOOK_POST_UPDATE) != DOME_RESULT_SUCCESS) {
    return EXIT_FAILURE;
  };
  // updateAudio()
  AUDIO_ENGINE_update(state->engine->audioEngine, state->vm);
  return EXIT_SUCCESS;
}

internal void
printTitle(ENGINE* engine) {
  ENGINE_printLog(engine, "DOME - Design-Oriented Minimalist Engine\n");
}

internal void
printVersion(ENGINE* engine) {
  ENGINE_printLog(engine, "Version: " DOME_VERSION " - " DOME_HASH "\n");
  SDL_version compiled;
  SDL_version linked;

  SDL_VERSION(&compiled);
  SDL_GetVersion(&linked);
  ENGINE_printLog(engine, "SDL version: %d.%d.%d (Compiled)\n", compiled.major, compiled.minor, compiled.patch);
  ENGINE_printLog(engine, "SDL version %d.%d.%d (Linked)\n", linked.major, linked.minor, linked.patch);
  ENGINE_printLog(engine, "Wren version: %d.%d.%d\n", WREN_VERSION_MAJOR, WREN_VERSION_MINOR, WREN_VERSION_PATCH);

  ENGINE_printLog(engine, "\n");
}


internal void
printUsage(ENGINE* engine) {
  ENGINE_printLog(engine, "\nUsage: \n");

  ENGINE_printLog(engine, "  dome [options]\n");
  ENGINE_printLog(engine, "  dome [options] [--] entry_path [arguments]\n");
  ENGINE_printLog(engine, "  dome -e | --embed sourceFile [moduleName] [destinationFile]\n");
  ENGINE_printLog(engine, "  dome -h | --help\n");
  ENGINE_printLog(engine, "  dome -v | --version\n");
  ENGINE_printLog(engine, "\nOptions: \n");
  ENGINE_printLog(engine, "  -b --buffer=<buf>   Set the audio buffer size (default: 11)\n");
#ifdef __MINGW32__
  ENGINE_printLog(engine, "  -c --console        Opens a console window for development.\n");
#endif
  ENGINE_printLog(engine, "  -d --debug          Enables debug mode.\n");
  ENGINE_printLog(engine, "  -e --embed          Converts a Wren source file to a C include file.\n");
  ENGINE_printLog(engine, "  -h --help           Show this screen.\n");
  ENGINE_printLog(engine, "  -r --record=<gif>   Record video to <gif>.\n");
  ENGINE_printLog(engine, "  -v --version        Show version.\n");
}

int main(int argc, char* args[])
{
  // configuring the buffer has to be first

  setbuf(stdout, NULL);
  setvbuf(stdout, NULL, _IONBF, 0);
  setbuf(stderr, NULL);
  setvbuf(stderr, NULL, _IONBF, 0);

  int result = EXIT_SUCCESS;
  WrenVM* vm = NULL;
  size_t gameFileLength;
  char* gameFile;
  INIT_TO_ZERO(ENGINE, engine);
  engine.record.gifName = "test.gif";
  engine.record.makeGif = false;
  INIT_TO_ZERO(LOOP_STATE, loop);
  loop.FPS = 60;
  loop.MS_PER_FRAME = ceil(1000.0 / loop.FPS);

  ENGINE_init(&engine);
  loop.engine = &engine;

  struct optparse_long longopts[] = {
    {"buffer", 'b', OPTPARSE_REQUIRED},
    #ifdef __MINGW32__
    {"console", 'c', OPTPARSE_NONE},
    #endif
    {"debug", 'd', OPTPARSE_NONE},
    {"embed", 'e', OPTPARSE_NONE},
    {"help", 'h', OPTPARSE_NONE},
    {"record", 'r', OPTPARSE_OPTIONAL},
    {"scale", 's', OPTPARSE_REQUIRED},
    {"version", 'v', OPTPARSE_NONE},
    {0}
  };

  int option;
  struct optparse options;
  optparse_init(&options, args);
  while ((option = optparse_long(&options, longopts, NULL)) != -1) {
    switch (option) {
      case 's':
        {
          int scale = atoi(options.optarg);
          if (scale <= 0) {
            // If it wasn't valid, set to a meaningful default.
            GIF_SCALE = 1;
          }
          GIF_SCALE = scale;
        } break;
      case 'b':
        {
          int shift = atoi(options.optarg);
          if (shift == 0) {
            // If it wasn't valid, set to a meaningful default.
            AUDIO_BUFFER_SIZE = 2048;
          }
          AUDIO_BUFFER_SIZE = 1 << shift;
        } break;
#ifdef __MINGW32__
      case 'c': {
          AllocConsole();
          freopen("CONIN$", "r", stdin);
          freopen("CONOUT$", "w", stdout);
          freopen("CONOUT$", "w", stderr);
      } break;
#endif
      case 'd':
        DEBUG_MODE = true;
        ENGINE_printLog(&engine, "Debug Mode enabled\n");
        break;
      case 'e':
        WRENEMBED_encodeAndDumpInDOME(argc, args);
        goto cleanup;
      case 'h':
        printTitle(&engine);
        printUsage(&engine);
        goto cleanup;
      case 'r':
        engine.record.makeGif = true;
        if (options.optarg != NULL) {
          engine.record.gifName = options.optarg;
        } else {
          engine.record.gifName = "dome.gif";
        }
        ENGINE_printLog(&engine, "GIF Recording is enabled: Saving to %s\n", engine.record.gifName);
        break;
      case 'v':
        printTitle(&engine);
        printVersion(&engine);
        goto cleanup;
      case '?':
        fprintf(stderr, "%s: %s\n", args[0], options.errmsg);
        result = EXIT_FAILURE;
        goto cleanup;
    }
  }

  {
    char* defaultEggName = "game.egg";
    char* mainFileName = "main.wren";

    char* base = BASEPATH_get();

    char pathBuf[PATH_MAX];
    char* fileName = NULL;

    // Get non-option args list
    engine.argv = calloc(max(2, argc), sizeof(char*));
    engine.argv[0] = args[0];
    engine.argv[1] = NULL;
    int domeArgCount = 1;
    char* otherArg = NULL;
    while ((otherArg = optparse_arg(&options))) {
      engine.argv[domeArgCount] = otherArg;
      domeArgCount++;
    }

    bool autoResolve = (domeArgCount == 1);

    domeArgCount = max(2, domeArgCount);
    engine.argv = realloc(engine.argv, sizeof(char*) * domeArgCount);
    engine.argc = domeArgCount;

    char* arg = NULL;
    if (domeArgCount > 1) {
      arg = engine.argv[1];
    }

    // Get establish the path components: filename(?) and basepath.
    if (arg != NULL) {
      strcpy(pathBuf, base);
      strcat(pathBuf, arg);
      if (isDirectory(pathBuf)) {
        BASEPATH_set(pathBuf);
        autoResolve = true;
      } else {
        char* dirc = strdup(pathBuf);
        char* basec = strdup(pathBuf);
        // This sets the filename used.
        fileName = strdup(basename(dirc));
        BASEPATH_set(dirname(basec));
        free(dirc);
        free(basec);
      }

      base = BASEPATH_get();
    }

    // If a filename is given in the path, use it, or assume its 'game.egg'
    strcpy(pathBuf, base);
    strcat(pathBuf, !autoResolve ? fileName : defaultEggName);

    if (doesFileExist(pathBuf)) {
      // the current path exists, let's see if it's a TAR file.
      engine.tar = malloc(sizeof(mtar_t));
      int tarResult = mtar_open(engine.tar, pathBuf, "r");
      if (tarResult == MTAR_ESUCCESS) {
        ENGINE_printLog(&engine, "Loading bundle %s\n", pathBuf);
        engine.argv[1] = strdup(pathBuf);
      } else {
        // Not a valid tar file.
        free(engine.tar);
        engine.tar = NULL;
      }
    }

    chdir(base);
    if (engine.tar != NULL) {
      // It is a tar file, we need to look for a "main.wren" entry point.
      strcpy(pathBuf, mainFileName);
    } else {
      // Not a tar file, use the given path or main.wren
      strcpy(pathBuf, base);
      strcat(pathBuf, !autoResolve ? fileName : mainFileName);
      engine.argv[1] = strdup(pathBuf);
      strcpy(pathBuf, !autoResolve ? fileName : mainFileName);
    }

    if (fileName != NULL) {
      free(fileName);
    }

    // The basepath is incorporated later, so we pass the basename version to this method.
    gameFile = ENGINE_readFile(&engine, pathBuf, &gameFileLength);
    if (gameFile == NULL) {
      if (engine.tar != NULL) {
        ENGINE_printLog(&engine, "Error: Could not load %s in bundle.\n", pathBuf);
      } else {
        ENGINE_printLog(&engine, "Error: Could not load %s.\n", pathBuf);
      }
      printUsage(&engine);
      result = EXIT_FAILURE;
      goto cleanup;
    }
  }

  result = ENGINE_start(&engine);
  if (result == EXIT_FAILURE) {
    goto cleanup;
  }

  // Configure Wren VM
  vm = VM_create(&engine);
  WrenInterpretResult interpreterResult;
  loop.vm = vm;

  // Load user game file
  SDL_Thread* recordThread = NULL;

  WrenHandle* initMethod = NULL;

  interpreterResult = wrenInterpret(vm, "main", gameFile);
  free(gameFile);
  if (interpreterResult != WREN_RESULT_SUCCESS) {
    result = EXIT_FAILURE;
    goto vm_cleanup;
  }
  // Load the class into slot 0.


  wrenEnsureSlots(vm, 3);
  initMethod = wrenMakeCallHandle(vm, "init()");
  wrenGetVariable(vm, "main", "Game", 0);
  loop.gameClass = wrenGetSlotHandle(vm, 0);
  loop.updateMethod = wrenMakeCallHandle(vm, "update()");
  loop.drawMethod = wrenMakeCallHandle(vm, "draw(_)");

  SDL_SetRenderDrawColor(engine.renderer, 0x00, 0x00, 0x00, 0xFF);

  // Initiate game loop

  wrenSetSlotHandle(vm, 0, loop.gameClass);
  interpreterResult = wrenCall(vm, initMethod);
  if (interpreterResult != WREN_RESULT_SUCCESS) {
    result = EXIT_FAILURE;
    goto vm_cleanup;
  }
  // Release this handle if it finished successfully
  wrenReleaseHandle(vm, initMethod);
  initMethod = NULL;
  engine.initialized = true;

  SDL_SetWindowPosition(engine.window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
  SDL_ShowWindow(engine.window);

  // Resizing from init must happen before we begin recording
  if (engine.record.makeGif) {
    recordThread = SDL_CreateThread(ENGINE_record, "DOMErecorder", &engine);
  }
  loop.lag = loop.MS_PER_FRAME;
  result = LOOP_processInput(&loop);
  if (result != EXIT_SUCCESS) {
    goto vm_cleanup;
  }
  loop.windowBlurred = false;
  uint64_t previousTime = SDL_GetPerformanceCounter();
  while (engine.running) {

    // processInput()
    if (loop.windowBlurred) {
      result = LOOP_processInput(&loop);
      if (result != EXIT_SUCCESS) {
        goto vm_cleanup;
      }
    }

    uint64_t currentTime = SDL_GetPerformanceCounter();
    loop.elapsed = 1000 * (currentTime - previousTime) / (double) SDL_GetPerformanceFrequency();
    previousTime = currentTime;


    // If we aren't focused, we skip the update loop and let the CPU sleep
    // to be good citizens
    if (loop.windowBlurred) {
      SDL_Delay(50);
      continue;
    }

    if(fabs(loop.elapsed - 1.0/120.0) < .0002){
      loop.elapsed = 1.0/120.0;
    }
    if(fabs(loop.elapsed - 1.0/60.0) < .0002){
      loop.elapsed = 1.0/60.0;
    }
    if(fabs(loop.elapsed - 1.0/30.0) < .0002){
      loop.elapsed = 1.0/30.0;
    }
    loop.lag += loop.elapsed;

    if (engine.lockstep) {
      if (loop.lag >= loop.MS_PER_FRAME) {
        result = LOOP_processInput(&loop);
        if (result != EXIT_SUCCESS) {
          goto vm_cleanup;
        }
        result = LOOP_update(&loop);
        if (result != EXIT_SUCCESS) {
          goto vm_cleanup;
        }
        result = LOOP_render(&loop);
        if (result != EXIT_SUCCESS) {
          goto vm_cleanup;
        }
        loop.lag = mid(0, loop.lag - loop.MS_PER_FRAME, loop.MS_PER_FRAME);
        LOOP_flip(&loop);
      }
    } else {
      loop.attempts = 5;
      while (loop.attempts > 0 && loop.lag >= loop.MS_PER_FRAME) {
        loop.attempts--;

        result = LOOP_processInput(&loop);
        if (result != EXIT_SUCCESS) {
          goto vm_cleanup;
        }
        // update()
        result = LOOP_update(&loop);
        if (result != EXIT_SUCCESS) {
          goto vm_cleanup;
        }
        loop.lag -= loop.MS_PER_FRAME;
      }
      // render();
      result = LOOP_render(&loop);
      if (result != EXIT_SUCCESS) {
        goto vm_cleanup;
      }
      if (loop.attempts == 0) {
        loop.lag = 0;
      }
      LOOP_flip(&loop);
    }


    if (!engine.vsyncEnabled) {
      SDL_Delay(1);
    }
  }

vm_cleanup:

  if (recordThread != NULL) {
    SDL_WaitThread(recordThread, NULL);
  }
  // Finish processing async threads so we can release resources
  ENGINE_finishAsync(&engine);
  SDL_Event event;
  while(SDL_PollEvent(&event)) {
    if (event.type == SDL_USEREVENT) {
      if (event.user.code == EVENT_LOAD_FILE) {
        FILESYSTEM_loadEventComplete(&event);
      }
    }
  }

  // Free resources
  ENGINE_reportError(&engine);

  if (initMethod != NULL) {
    wrenReleaseHandle(vm, initMethod);
  }

  LOOP_release(&loop);

  if (bufferClass != NULL) {
    wrenReleaseHandle(vm, bufferClass);
  }

  INPUT_release(vm);

  AUDIO_ENGINE_halt(engine.audioEngine);
  AUDIO_ENGINE_releaseHandles(engine.audioEngine, vm);

cleanup:
  BASEPATH_free();
  VM_free(vm);
  result = engine.exit_status;
  ENGINE_free(&engine);
  //Quit SDL subsystems
  if (strlen(SDL_GetError()) > 0) {
    SDL_Quit();
  }

  return result;
}
<file_sep>/docs/modules/audio.md
[< Back](.)

audio
================

The `audio` module lets you play audio files such as music or sound effects.

It contains the following classes:

* [AudioEngine](#audioengine)
* [AudioChannel](#audiochannel)
* [AudioState](#audiostate)

## AudioEngine

DOME supports playback of audio files in OGG and WAV formats. It will convert all files to its native sample rate of 44.1kHz (CD quality audio), but the re-sampling algorithm used is naive and may introduce audio artifacts. It is recommended that you produce your audio with a 44.1kHz sample-rate, for the best quality audio.

An audio file is loaded from disk into memory using the `load` function, and remains in memory until you call `unload(_)` or `unloadAll()`, or when DOME closes. If the audio needs to be resampled, this may take some time and block the main thread.

When an audio file is about to be played, DOME allocates it an "audio channel", which handles the settings for volume, looping and panning.
Once the audio is stopped or finishes playing, that channel is no longer usable, and a new one will need to be acquired.


### Example

```wren
AudioEngine.load("fire", "res/Laser_Shoot.wav")
var channel = AudioEngine.play("fire")
channel.stop()

...

AudioEngine.unload("fire")
```

### Methods

#### `static register(name: String, path: String)`
DOME keeps a mapping from a developer-friendly name to the file path. Calling this method sets up this mapping, but doesn't load that file into memory.

#### `static load(name: String)`
If the `name` has been mapped to a file path, DOME will load that file into memory, ready to play.

#### `static load(name: String, path: String)`
This combines the `register(_,_)` and `load(_)` calls, for convenience.

#### `static play(name: String): AudioChannel`
Plays the named audio sample once, at maximum volume, with equal pan.
#### `static play(name: String, volume: Number): AudioChannel`
Plays the named audio sample once, at _volume_, with equal pan.
#### `static play(name: String, volume: Number, loop: Boolean): AudioChannel`
Plays the named audio sample, at _volume_, with equal pan. If _loop_ is set, the sample will repeat once playback completes.
#### `static play(name: String, volume: Number, loop: Boolean, pan: Number): AudioChannel`
Play the named audio sample and returns the channel object representing that playback.
The other parameters are explained in the [AudioChannel](#audiochannel) api.

#### `static stopAllChannels()`
Stop all playing audio channels.

#### `static unloadAll()`
Releases the resources of the all currently loaded audio samples. This will halt any audio using that sample immediately.

#### `static unload(name: String)`
Releases the resources of the chosen audio sample. This will halt any audio using that sample immediately.

## AudioChannel

These are created when you `play` a buffer of AudioData using the AudioEngine. You cannot construct these directly. 

A playing audio channel has three main properties:
 * _volume_ - A value with minimum 0.0 for the volume.
 * _loop_ - If true, the audio channel will loop once it is complete.
 * _pan_ - A value between -1.0 and 1.0 which divides the audio playback between left and right stereo channels.

### Instance Fields

#### `finished: Boolean`
Returns true if the audio channel has finished playing. It cannot be restarted after this point.

#### `length: Number`
The total number of samples in this channel's audio buffer.
You should divide this by `44100` to get the length in seconds.

#### `loop: Boolean`
You can set this to control whether the sample will loop once it completes, or stop.
The channel will become invalid if it reaches the end of the sample and `loop` is false.

#### `pan: Number`
You can read and modify the pan position, as a bounded value between -1.0 and 1.0.

#### `position: Number`
This marks the position of the next sample to be loaded into the AudioEngine mix buffer (which happens on a seperate thread).
You can set a new position for the channel, but it isn't going to be exact, due to delays in audio processing.

You should divide this by `44100` to get the position in seconds.

#### `soundId: String`
This is the sample name used for this sound.

#### `state: AudioState`
This is an enum which represents the current state of the audio.

#### `volume: Number`
This returns a number with a minimum of 0.0 representing the volume of the channel. 1.0 is the default for the audio data.

You can set this to change the volume.

### Instance Methods

#### `stop(): Void`
Requests that the channel stops as soon as possible.

## AudioState
AudioChannel objects can be in one of the following states:

 - AudioState.INITIALIZE
 - AudioState.TO_PLAY
 - AudioState.PLAYING
 - AudioState.STOPPING
 - AudioState.STOPPED

<file_sep>/src/audio/engine.h
typedef enum {
  AUDIO_TYPE_UNKNOWN,
  AUDIO_TYPE_WAV,
  AUDIO_TYPE_OGG
} AUDIO_TYPE;

struct CHANNEL_t {
  volatile CHANNEL_STATE state;
  CHANNEL_REF ref;
  bool stopRequested;
  CHANNEL_mix mix;
  CHANNEL_callback update;
  CHANNEL_callback finish;

  void* userdata;
};

typedef struct {
  SDL_AudioSpec spec;
  AUDIO_TYPE audioType;
  // Length is the number of LR samples
  uint32_t length;
  // Audio is stored as a stream of interleaved normalised values from [-1, 1)
  float* buffer;
} AUDIO_DATA;

struct AUDIO_CHANNEL_PROPS {
  // Control variables
  bool loop;
  // Playback variables
  float volume;
  float pan;
  // Position is the sample value to play next
  volatile size_t position;
  bool resetPosition;
};

typedef struct {
  struct AUDIO_CHANNEL_PROPS current;
  struct AUDIO_CHANNEL_PROPS new;
  char* soundId;
  float actualVolume;
  float actualPan;
  bool fade;

  AUDIO_DATA* audio;
  WrenHandle* audioHandle;
} AUDIO_CHANNEL;

internal void AUDIO_CHANNEL_mix(CHANNEL_REF ref, float* stream, size_t totalSamples);
internal void AUDIO_CHANNEL_update(CHANNEL_REF ref, WrenVM* vm);
internal void AUDIO_CHANNEL_finish(CHANNEL_REF ref, WrenVM* vm);
internal void CHANNEL_requestStop(CHANNEL* channel);
internal void* CHANNEL_getData(CHANNEL* channel);
internal inline bool CHANNEL_isPlaying(CHANNEL* channel);
<file_sep>/src/util/embed.c
#include "wrenembed.c"
#include <string.h>

int main(int argc, char* args[])
{
  if (argc < 2) {
    printf("./embed sourceFile [moduleName] [destinationFile]\n");
    printf("Not enough arguments.\n");
    return EXIT_FAILURE;
  }
  
  return WRENEMBED_encodeAndDump(argc, args);
}



<file_sep>/docs/guides/distribution.md
[< Back](..)

Distributing your games
===================

DOME is designed to be cross-platform, and so the same Wren game files and assets should work across Windows, Mac and Linux. On Mac and Linux, the SDL2 shared library will need to be provided.

## Basic Packaging
The easiest way to share a game you've made is to place a DOME binary, your source code and game assets into a single zip file. Do this once for each platform you wish to support, and share those zip files with your users.

## NEST - Easy bundling

_(Please note: NEST is still in development, and has to be built from source)_

For easy distribution, you can package all of your games resources into a single `.egg` file using a tool called [NEST](https://github.com/domeengine/nest). DOME automatically plays any file named `game.egg` in the current working directory.

If you use a `.egg` file, DOME expects your game to start from a `main.wren` in the base directory as it's entry point.

Install NEST, and then navigate to your main game directory, before running the following:

```
> nest -z -o game.egg -- [files | directories]
```

## Cross-Platform Distribution
_(You can ignore this section if you are using pre-compiled DOME binaries.)_

This section discusses the needs of various platforms when distributing games with DOME.

### Windows

On Windows, DOME comes compiled with all it's dependancies, so you just need to provide your game files.

### Mac OS X

On Mac, the SDL2 library needs to be provided. This can either be globally installed, or you can package it as part of an application bundle, using the following layout:

```
+-- Game.app
    +-- Contents
        +-- Info.plist
        +-- MacOS
            +-- dome
            +-- libSDL2.dylib
            +-- Game (This is a small runscript)
        +-- Resources
            +-- game.egg
            +-- icon.icns
```

The runscript is very simple and looks like this:
```bash
#!/bin/bash
cd "${0%/*}"
./dome
```

Finally, the Info.plist at minimum needs to look like this:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>CFBundleExecutable</key>
  <string>Game</string>
  <key>CFBundleIconFile</key>
  <string>icon</string>
  <key>NSHighResolutionCapable</key>
  <true/>
</dict>
</plist>
```

The .app bundle, runscript and the `CFBundleExecutable` must all be named the same.

Doing this results in a self contained and easy to distribute application bundle.

### Linux

To run your game on linux, make sure SDL2 is installed, then run the dome executable with a `game.egg` file in the same directory.

<file_sep>/src/engine.c
internal void
getColorComponents(uint32_t color, uint8_t *r, uint8_t *g, uint8_t *b) {
  *r = color & 0xFF;
  *g = (color & (0xFF << 8)) >> 8;
  *b = (color & (0xFF << 16)) >> 16;
}

internal int
ENGINE_record(void* ptr) {
  // Thread: Seperate gif record
  ENGINE* engine = ptr;
  size_t imageSize = engine->canvas.width * engine->canvas.height;
  engine->record.gifPixels = (uint32_t*)malloc(imageSize*4*sizeof(uint8_t));
  size_t scale = GIF_SCALE;
  uint32_t* scaledPixels = (uint32_t*)malloc(imageSize*4*sizeof(uint8_t)* scale * scale);
  CANVAS canvas = engine->canvas;

  jo_gif_t gif = jo_gif_start(engine->record.gifName, canvas.width * scale, canvas.height * scale, 0, 31);
  uint8_t FPS = 30;
  double MS_PER_FRAME = ceil(1000.0 / FPS);
  double lag = 0;
  uint64_t previousTime = SDL_GetPerformanceCounter();
  do {
    SDL_Delay(1);
    uint64_t currentTime = SDL_GetPerformanceCounter();
    double elapsed = 1000 * (currentTime - previousTime) / (double)SDL_GetPerformanceFrequency();
    previousTime = currentTime;
    if(fabs(elapsed - 1.0/120.0) < .0002){
      elapsed = 1.0/120.0;
    }
    if(fabs(elapsed - 1.0/60.0) < .0002){
      elapsed = 1.0/60.0;
    }
    if(fabs(elapsed - 1.0/30.0) < .0002){
      elapsed = 1.0/30.0;
    }
    lag += elapsed;
    if (lag >= MS_PER_FRAME) {
      if (scale > 1) {
        for (size_t j = 0; j < canvas.height * scale; j++) {
          for (size_t i = 0; i < canvas.width * scale; i++) {
            size_t u = i / scale;
            size_t v = j / scale;
            int32_t c = ((uint32_t*)engine->record.gifPixels)[v * canvas.width + u];
            scaledPixels[j * canvas.width * scale + i] = c;
          }
        }
        jo_gif_frame(&gif, (uint8_t*)scaledPixels, 4, true);
      } else {
        jo_gif_frame(&gif, (uint8_t*)engine->record.gifPixels, 3, true);
      }
      lag -= MS_PER_FRAME;
    }
  } while(engine->running);

  jo_gif_end(&gif);
  free(engine->record.gifPixels);
  return 0;
}

internal void
ENGINE_openLogFile(ENGINE* engine) {
  // DOME-2020-02-02-090000.log
  char* filename = "DOME-out.log";
  engine->debug.logFile = fopen(filename, "w+");
}

internal void
ENGINE_printLogVariadic(ENGINE* engine, const char* line, va_list argList) {
  va_list args;
  va_copy(args, argList);
  size_t bufSize = vsnprintf(NULL, 0, line, args) + 1;
  va_end(args);

  char buffer[bufSize];
  buffer[0] = '\0';
  va_copy(args, argList);
  vsnprintf(buffer, bufSize, line, args);
  va_end(args);

  // Output to console
  printf("%s", buffer);

  if (engine->debug.logFile == NULL) {
    ENGINE_openLogFile(engine);
  }
  if (engine->debug.logFile != NULL) {
    // Output to file
    fputs(buffer, engine->debug.logFile);
    fflush(engine->debug.logFile);
  }
}

internal void
ENGINE_printLog(ENGINE* engine, char* line, ...) {
  // Args is mutated by each vsnprintf call,
  // so it needs to be reinitialised.
  va_list args;
  va_start(args, line);
  ENGINE_printLogVariadic(engine, line, args);
  va_end(args);
}

internal ENGINE_WRITE_RESULT
ENGINE_writeFile(ENGINE* engine, const char* path, const char* buffer, size_t length) {
  const char* fullPath;
  if (path[0] != '/') {
    const char* base = BASEPATH_get();
    fullPath = malloc(strlen(base)+strlen(path)+1);
    strcpy((void*)fullPath, base); /* copy name into the new var */
    strcat((void*)fullPath, path); /* add the extension */
  } else {
    fullPath = path;
  }

  ENGINE_printLog(engine, "Writing to filesystem: %s\n", path);
  int result = writeEntireFile(fullPath, buffer, length);
  if (result == ENOENT) {
    result = ENGINE_WRITE_PATH_INVALID;
  } else {
    result = ENGINE_WRITE_SUCCESS;
  }

  if (path[0] != '/') {
    free((void*)fullPath);
  }

  return result;
}

internal char*
ENGINE_readFile(ENGINE* engine, const char* path, size_t* lengthPtr) {
  char pathBuf[PATH_MAX];

  if (strncmp(path, "./", 2) == 0) {
    strcpy(pathBuf, path + 2);
  } else {
    strcpy(pathBuf, path);
  }

  if (engine->tar != NULL) {
    ENGINE_printLog(engine, "Reading from bundle: %s\n", pathBuf);

    char* file = NULL;
    int err = readFileFromTar(engine->tar, pathBuf, lengthPtr, &file);
    if (err == MTAR_ESUCCESS) {
      return file;
    }

    if (DEBUG_MODE) {
      ENGINE_printLog(engine, "Couldn't read %s from bundle: %s. Falling back\n", pathBuf, mtar_strerror(err));
    }
  }

  if (path[0] != '/') {
    strcpy(pathBuf, BASEPATH_get());
    strcat(pathBuf, path);
  }

  if (!doesFileExist(pathBuf)) {
    return NULL;
  }

  ENGINE_printLog(engine, "Reading from filesystem: %s\n", pathBuf);
  return readEntireFile(pathBuf, lengthPtr);
}

internal int
ENGINE_taskHandler(ABC_TASK* task) {
  if (task->type == TASK_PRINT) {
    printf("%s\n", (char*)task->data);
    task->resultCode = 0;
    // TODO: Push to SDL Event Queue
  } else if (task->type == TASK_LOAD_FILE) {
    FILESYSTEM_loadEventHandler(task->data);
  } else if (task->type == TASK_WRITE_FILE) {
  }
  return 0;
}

internal bool
ENGINE_setupRenderer(ENGINE* engine, bool vsync) {
  engine->vsyncEnabled = vsync;
  if (engine->renderer != NULL) {
    SDL_DestroyRenderer(engine->renderer);
  }

  int flags = SDL_RENDERER_ACCELERATED;
  if (vsync) {
    flags |= SDL_RENDERER_PRESENTVSYNC;
  }
  engine->renderer = SDL_CreateRenderer(engine->window, -1, flags);
  if (engine->renderer == NULL) {
    return false;
  }
  SDL_RenderSetLogicalSize(engine->renderer, engine->canvas.width, engine->canvas.height);

  engine->texture = SDL_CreateTexture(engine->renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, engine->canvas.width, engine->canvas.height);
  if (engine->texture == NULL) {
    return false;
  }
  SDL_RenderGetViewport(engine->renderer, &(engine->viewport));
  return true;
}

internal ENGINE*
ENGINE_init(ENGINE* engine) {
  engine->window = NULL;
  engine->renderer = NULL;
  engine->texture = NULL;

  engine->blitBuffer.pixels = calloc(0, 0);
  engine->blitBuffer.width = 0;
  engine->blitBuffer.height = 0;

  engine->lockstep = false;
  engine->debug.avgFps = 58;
  engine->debugEnabled = false;
  engine->debug.alpha = 0.9;

  engine->debug.errorBufMax = 0;
  engine->debug.errorBuf = NULL;
  engine->debug.errorBufLen = 0;

  // Initialise the canvas offset.
  engine->canvas.pixels = NULL;
  engine->canvas.offsetX = 0;
  engine->canvas.offsetY = 0;
  engine->canvas.width = GAME_WIDTH;
  engine->canvas.height = GAME_HEIGHT;
  DOME_RECT clip = {
    .x = 0,
    .y = 0,
    .w = GAME_WIDTH,
    .h = GAME_HEIGHT
  };
  engine->canvas.clip = clip;

  engine->argv = NULL;
  engine->argc = 0;

  // TODO: handle if we can't allocate memory.
  PLUGIN_COLLECTION_init(engine);

  return engine;
}

internal int
ENGINE_start(ENGINE* engine) {
  int result = EXIT_SUCCESS;
#if defined _WIN32
  SDL_setenv("SDL_AUDIODRIVER", "directsound", true);
#endif
  SDL_SetHint(SDL_HINT_MOUSE_RELATIVE_MODE_WARP, "1");

  //Initialize SDL
  if (SDL_Init(SDL_INIT_VIDEO) < 0) {
    ENGINE_printLog(engine, "SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
    result = EXIT_FAILURE;
    goto engine_init_end;
  }

  //Create window
  engine->window = SDL_CreateWindow("DOME", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE);
  if(engine->window == NULL)
  {
    char* message = "Window could not be created! SDL_Error: %s\n";
    ENGINE_printLog(engine, message, SDL_GetError());
    result = EXIT_FAILURE;
    goto engine_init_end;
  }

  ENGINE_setupRenderer(engine, true);
  if (engine->renderer == NULL)
  {
    char* message = "Could not create a renderer: %s";
    ENGINE_printLog(engine, message, SDL_GetError());
    result = EXIT_FAILURE;
    goto engine_init_end;
  }

  engine->canvas.pixels = calloc(engine->canvas.width * engine->canvas.height, sizeof(char) * 4);
  if (engine->canvas.pixels == NULL) {
    result = EXIT_FAILURE;
    goto engine_init_end;
  }

  engine->audioEngine = AUDIO_ENGINE_init();
  if (engine->audioEngine == NULL) {
    result = EXIT_FAILURE;
    goto engine_init_end;
  }

  ENGINE_EVENT_TYPE = SDL_RegisterEvents(1);

  ABC_FIFO_create(&engine->fifo);
  engine->fifo.taskHandler = ENGINE_taskHandler;

  MAP_init(&engine->moduleMap);

  engine->running = true;

engine_init_end:
  return result;

}

internal void
ENGINE_finishAsync(ENGINE* engine) {
  if (!engine->fifo.shutdown) {
    ABC_FIFO_close(&engine->fifo);
  }
}

internal void
ENGINE_free(ENGINE* engine) {

  if (engine == NULL) {
    return;
  }


  ENGINE_finishAsync(engine);

  PLUGIN_COLLECTION_free(engine);

  if (engine->audioEngine) {
    AUDIO_ENGINE_free(engine->audioEngine);
    free(engine->audioEngine);
    engine->audioEngine = NULL;
  }

  if (engine->tar != NULL) {
    mtar_close(engine->tar);
  }

  if (engine->moduleMap.head != NULL) {
    MAP_free(&engine->moduleMap);
  }

  if (engine->blitBuffer.pixels != NULL) {
    free(engine->blitBuffer.pixels);
  }

  if (engine->canvas.pixels != NULL) {
    free(engine->canvas.pixels);
  }

  if (engine->texture != NULL) {
    SDL_DestroyTexture(engine->texture);
  }

  if (engine->renderer != NULL) {
    SDL_DestroyRenderer(engine->renderer);
  }

  if (engine->window != NULL) {
    SDL_DestroyWindow(engine->window);
  }

  if (engine->argv != NULL) {
    free(engine->argv[1]);
    free(engine->argv);
  }

  // DEBUG features
  if (engine->debug.logFile != NULL) {
    fclose(engine->debug.logFile);
  }

  if (engine->debug.errorBuf != NULL) {
    free(engine->debug.errorBuf);
  }
}

internal uint32_t
ENGINE_pget(ENGINE* engine, int64_t x, int64_t y) {
  int32_t width = engine->canvas.width;
  int32_t height = engine->canvas.height;
  if (0 <= x && x < width && 0 <= y && y < height) {
    return ((uint32_t*)(engine->canvas.pixels))[width * y + x];
  }
  return 0xFF000000;
}

inline internal void
ENGINE_pset(ENGINE* engine, int64_t x, int64_t y, uint32_t c) {

  // Account for canvas offset
  x += engine->canvas.offsetX;
  y += engine->canvas.offsetY;

  // Draw pixel at (x,y)
  int32_t width = engine->canvas.width;
  DOME_RECT zone = engine->canvas.clip;

  uint8_t newA = ((0xFF000000 & c) >> 24);

  if (newA == 0) {
    return;
  } else if (zone.x <= x && x < zone.x + zone.w && zone.y <= y && y < zone.y + zone.h) {
    if (newA < 0xFF) {
      uint32_t current = ((uint32_t*)(engine->canvas.pixels))[width * y + x];
      double normA = newA / (double)UINT8_MAX;
      double diffA = 1 - normA;

      uint8_t oldR, oldG, oldB, newR, newG, newB;
      getColorComponents(current, &oldR, &oldG, &oldB);
      getColorComponents(c, &newR, &newG, &newB);

      uint8_t a = 0xFF;
      uint8_t r = (diffA * oldR + normA * newR);
      uint8_t g = (diffA * oldG + normA * newG);
      uint8_t b = (diffA * oldB + normA * newB);

      c = (a << 24) | (b << 16) | (g << 8) | r;
    }

    // This is a very hot line, so we use pointer arithmetic for
    // speed!
    *(((uint32_t*)engine->canvas.pixels) + (width * y + x)) = c;
  }
}

internal void
ENGINE_blitBuffer(ENGINE* engine, int32_t x, int32_t y) {
  PIXEL_BUFFER buffer = engine->blitBuffer;

  uint32_t* blitBuffer = buffer.pixels;
  for (size_t j = 0; j < buffer.height; j++) {
    for (size_t i = 0; i < buffer.width; i++) {
      uint32_t c = *(blitBuffer + (j * buffer.width + i));
      ENGINE_pset(engine, x + i, y + j, c);
    }
  }
}

internal uint32_t*
ENGINE_resizeBlitBuffer(ENGINE* engine, size_t width, size_t height) {
  PIXEL_BUFFER* buffer = &engine->blitBuffer;

  size_t oldBufferSize = buffer->width * buffer->height * sizeof(uint32_t);
  size_t newBufferSize = width * height * sizeof(uint32_t);

  if (oldBufferSize < newBufferSize) {
    buffer->pixels = realloc(buffer->pixels, newBufferSize);
    oldBufferSize = newBufferSize;
  }
  memset(buffer->pixels, 0, oldBufferSize);
  buffer->width = width;
  buffer->height = height;
  return buffer->pixels;
}

internal unsigned const char*
defaultFontLookup(utf8_int32_t codepoint) {
  const local_persist unsigned char empty[8] = { 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F };
  if (codepoint >= 0 && codepoint < 0x7F) {
    return font8x8_basic[codepoint];
  } else if (codepoint >= 0x80 && codepoint <= 0x9F) {
    codepoint = codepoint - 0x80;
    return font8x8_control[codepoint];
  } else if (codepoint >= 0xA0 && codepoint <= 0xFF) {
    codepoint = codepoint - 0xA0;
    return font8x8_ext_latin[codepoint];
  } else if (codepoint >= 0x390 && codepoint <= 0x3C9) {
    codepoint = codepoint - 0x390;
    return font8x8_greek[codepoint];
  } else if (codepoint >= 0x2500 && codepoint <= 0x257F) {
    codepoint = codepoint - 0x2500;
    return font8x8_box[codepoint];
  } else if (codepoint >= 0x2580 && codepoint <= 0x259F) {
    codepoint = codepoint - 0x2580;
    return font8x8_block[codepoint];
  } else if (codepoint >= 0x3040 && codepoint <= 0x309F) {
    codepoint = codepoint - 0x3040;
    return font8x8_hiragana[codepoint];
  } else if (codepoint >= 0xE541 && codepoint <= 0xE55A) {
    codepoint = codepoint - 0xE541;
    return font8x8_sga[codepoint];
  } else {
    return empty;
  }
}

internal void
ENGINE_print(ENGINE* engine, char* text, int64_t x, int64_t y, uint32_t c) {
  int fontWidth = 8;
  int fontHeight = 8;
  int spacing = (fontHeight / 4);
  int cursor = 0;
  utf8_int32_t codepoint;
  void* v = utf8codepoint(text, &codepoint);
  size_t len = utf8len(text);
  for (size_t pos = 0; pos < len; pos++) {
    if (text[pos] == '\n') {
      cursor = 0;
      y += fontHeight + spacing;
      v = utf8codepoint(v, &codepoint);
      continue;
    }

    uint8_t* glyph = (uint8_t*)defaultFontLookup(codepoint);
    for (int j = 0; j < fontHeight; j++) {
      for (int i = 0; i < fontWidth; i++) {
        uint8_t v = (glyph[j] >> i) & 1;
        if (v != 0) {
          ENGINE_pset(engine, x + cursor + i, y + j, c);
        }
      }
    }
    cursor += fontWidth;
    v = utf8codepoint(v, &codepoint);
  }
}

internal void
blitPixel(void* dest, size_t pitch, int64_t x, int64_t y, uint32_t c) {
  uint32_t* pixel = (uint32_t*)dest + (y * pitch + x);
  *pixel = c;
}

internal void
blitLine(void* dest, size_t destPitch, int64_t x, int64_t y, int64_t w, uint32_t* buf) {
  size_t pitch = destPitch;
  char* pixels = dest;
  int64_t startX = mid(0, x, pitch);
  int64_t endX = mid(0, x + w, pitch);
  size_t lineWidth = endX - startX;
  uint32_t* bufStart = buf + (size_t)fabs(fmin(0, x));
  char* line = pixels + ((y * pitch + startX) * 4);
  memcpy(line, bufStart, lineWidth * 4);
}

internal void
ENGINE_blitLine(ENGINE* engine, int64_t x, int64_t y, int64_t w, uint32_t* buf) {
  CANVAS canvas = engine->canvas;
  DOME_RECT zone = canvas.clip;
  y += canvas.offsetY;
  if (y < max(0, zone.y) || y >= min(canvas.height, zone.y + zone.h)) {
    return;
  }

  int64_t offsetX = canvas.offsetX;
  size_t pitch = canvas.width;

  int64_t screenStart = max(0, zone.x);
  int64_t lineEnd = mid(0, zone.x + zone.w, pitch);
  int64_t screenX = x + offsetX;

  int64_t startX = mid(screenStart, screenX, lineEnd);
  int64_t endX = mid(screenStart, screenX + w, lineEnd);
  int64_t lineWidth = max(0, min(endX, pitch) - startX);

  uint32_t* bufStart = buf;
  char* pixels = canvas.pixels;
  char* line = pixels + ((y * pitch + startX) * 4);
  memcpy(line, bufStart, lineWidth * 4);
}

internal uint32_t*
ENGINE_createMask(ENGINE* engine, uint64_t originalSize, uint32_t color) {
  uint64_t size = originalSize;
  uint32_t* mask = ENGINE_resizeBlitBuffer(engine, size, size);
  for (uint64_t y = 0; y < size; y++) {
    for (uint64_t x = 0; x < size; x++) {
      blitPixel(mask, size, x, y, color);
    }
  }
  return mask;
}

internal void
ENGINE_drawMask(ENGINE* engine, int64_t x, int64_t y) {
  size_t offset = engine->blitBuffer.width / 2;
  ENGINE_blitBuffer(engine, x - offset, y - offset);
}

internal void
ENGINE_line_high(ENGINE* engine, int64_t x1, int64_t y1, int64_t x2, int64_t y2, uint32_t c) {
  int64_t dx = x2 - x1;
  int64_t dy = y2 - y1;
  int64_t xi = 1;
  if (dx < 0) {
    xi = -1;
    dx = -dx;
  }
  int64_t p = 2 * dx - dy;

  int64_t y = y1;
  int64_t x = x1;
  while(y <= y2) {
    ENGINE_drawMask(engine, x, y);
    if (p > 0) {
      x += xi;
      p = p - 2 * dy;
    }
    p = p + 2 * dx;
    y++;
  }
}

internal void
ENGINE_line_low(ENGINE* engine, int64_t x1, int64_t y1, int64_t x2, int64_t y2, uint32_t c) {
  int64_t dx = x2 - x1;
  int64_t dy = y2 - y1;
  int64_t yi = 1;
  if (dy < 0) {
    yi = -1;
    dy = -dy;
  }
  int64_t p = 2 * dy - dx;

  int64_t y = y1;
  int64_t x = x1;
  while(x <= x2) {
    ENGINE_drawMask(engine, x, y);
    if (p > 0) {
      y += yi;
      p = p - 2 * dx;
    }
    p = p + 2 * dy;
    x++;
  }
}

internal void
ENGINE_line(ENGINE* engine, int64_t x1, int64_t y1, int64_t x2, int64_t y2, uint32_t c, uint64_t size) {
  ENGINE_createMask(engine, size, c);

  if (llabs(y2 - y1) < llabs(x2 - x1)) {
    if (x1 > x2) {
      ENGINE_line_low(engine, x2, y2, x1, y1, c);
    } else {
      ENGINE_line_low(engine, x1, y1, x2, y2, c);
    }
  } else {
    if (y1 > y2) {
      ENGINE_line_high(engine, x2, y2, x1, y1, c);
    } else {
      ENGINE_line_high(engine, x1, y1, x2, y2, c);
    }

  }
}

internal void
ENGINE_circle_filled(ENGINE* engine, int64_t x0, int64_t y0, int64_t r, uint32_t c) {
  int64_t x = 0;
  int64_t y = r;
  int64_t d = round(M_PI - (2*r));


  uint16_t alpha = (0xFF000000 & c) >> 24;
  size_t bufWidth = r * 2 + 1;
  uint32_t buf[bufWidth];
  for (size_t i = 0; i < bufWidth; i++) {
    buf[i] = c;
  }
  if (alpha == 0xFF) {
    while (x <= y) {
      size_t lineWidthX = x * 2 + 1;
      size_t lineWidthY = y * 2 + 1;

      ENGINE_blitLine(engine, x0 - x, y0 + y, lineWidthX, buf);
      ENGINE_blitLine(engine, x0 - x, y0 - y, lineWidthX, buf);

      ENGINE_blitLine(engine, x0 - y, y0 + x, lineWidthY, buf);
      ENGINE_blitLine(engine, x0 - y, y0 - x, lineWidthY, buf);

      if (d < 0) {
        d = d + (M_PI * x) + (M_PI * 2);
      } else {
        d = d + (M_PI * (x - y)) + (M_PI * 3);
        y--;
      }
      x++;
    }
  } else {
    uint32_t* blitBuffer = ENGINE_resizeBlitBuffer(engine, bufWidth, bufWidth);
    size_t pitch = engine->blitBuffer.width;
    while (x <= y) {
      int64_t c = r;
      size_t lineWidthX = x * 2 + 1;
      size_t lineWidthY = y * 2 + 1;
      blitLine(blitBuffer, pitch, c - x, c + y, lineWidthX, buf);
      if (y != 0) {
        blitLine(blitBuffer, pitch, c - x, c - y, lineWidthX, buf);
      }
      blitLine(blitBuffer, pitch, c - y, c + x, lineWidthY, buf);
      if (x != 0) {
        blitLine(blitBuffer, pitch, c - y, c - x, lineWidthY, buf);
      }

      if (d < 0) {
        d = d + (M_PI * x) + (M_PI * 2);
      } else {
        d = d + (M_PI * (x - y)) + (M_PI * 3);
        y--;
      }
      x++;
    }
    ENGINE_blitBuffer(engine, x0 - r, y0 - r);
  }
}

internal void
ENGINE_circle(ENGINE* engine, int64_t x0, int64_t y0, int64_t r, uint32_t c) {
  int64_t x = 0;
  int64_t y = r;
  int64_t d = round(M_PI - (2*r));

  size_t pitch = r * 2 + 1;
  uint32_t* blitBuffer = ENGINE_resizeBlitBuffer(engine, pitch, pitch);
  pitch = engine->blitBuffer.width;

  while (x <= y) {
    blitPixel(blitBuffer, pitch, r + x, r + y , c);
    blitPixel(blitBuffer, pitch, r - x, r - y , c);
    blitPixel(blitBuffer, pitch, r - x, r + y , c);
    blitPixel(blitBuffer, pitch, r + x, r - y , c);
    blitPixel(blitBuffer, pitch, r + y, r + x , c);
    blitPixel(blitBuffer, pitch, r - y, r - x , c);
    blitPixel(blitBuffer, pitch, r - y, r + x , c);
    blitPixel(blitBuffer, pitch, r + y, r - x , c);

    if (d < 0) {
      d = d + (M_PI * x) + (M_PI * 2);
    } else {
      d = d + (M_PI * (x - y)) + (M_PI * 3);
      y--;
    }
    x++;
  }
  ENGINE_blitBuffer(engine, x0 - r, y0 - r);
}

internal inline double
ellipse_getRegion(double x, double y, int32_t rx, int32_t ry) {
  double rxSquare = rx * rx;
  double rySquare = ry * ry;
  return (rySquare*x) / (rxSquare*y);
}

internal void
ENGINE_ellipsefill(ENGINE* engine, int64_t x0, int64_t y0, int64_t x1, int64_t y1, uint32_t c) {

  // Calculate radius
  int64_t swap = x1;
  if (x1 < x0) {
    x1 = x0;
    x0 = swap;
  }
  swap = y1;
  if (y1 < y0) {
    y1 = y0;
    y0 = swap;
  }

  int32_t rx = (x1 - x0) / 2; // Radius on x
  int32_t ry = (y1 - y0) / 2; // Radius on y
  uint32_t rxSquare = rx*rx;
  uint32_t rySquare = ry*ry;
  uint32_t rx2ry2 = rxSquare * rySquare;

  int32_t dx = (rx + 1) * 2;
  int32_t dy = (ry + 1) * 2;

  size_t bufWidth = max(dx, dy);
  uint32_t buf[bufWidth];
  for (size_t i = 0; i < bufWidth; i++) {
    buf[i] = c;
  }
  uint32_t* blitBuffer = ENGINE_resizeBlitBuffer(engine, dx, dy);
  size_t pitch = engine->blitBuffer.width;

  // Start drawing at (0, ry)
  int32_t x = 0;
  int32_t y = ry;
  double d = 0;

  while (fabs(ellipse_getRegion(x, y, rx, ry)) < 1) {
    x++;
    size_t lineWidthX = x * 2 + 1;
    double xSquare = x*x;
    // evaluate decision parameter
    d = rySquare * xSquare + rxSquare * pow(y - 0.5, 2) - rx2ry2;

    if (d > 0) {
      y--;
    }
    blitLine(blitBuffer, pitch, rx - x, ry + y, lineWidthX, buf);
    blitLine(blitBuffer, pitch, rx - x, ry - y, lineWidthX, buf);
  }

  while (y > 0) {
    y--;
    double ySquare = y*y;
    // evaluate decision parameter
    d = rxSquare * ySquare + rySquare * pow(x + 0.5, 2) - rx2ry2;

    if (d <= 0) {
      x++;
    }
    size_t lineWidthY = x * 2 + 1;
    blitLine(blitBuffer, pitch, rx - x, ry + y, lineWidthY, buf);
    blitLine(blitBuffer, pitch, rx - x, ry - y, lineWidthY, buf);
  };

  ENGINE_blitBuffer(engine, x0, y0);
}

internal void
ENGINE_ellipse(ENGINE* engine, int64_t x0, int64_t y0, int64_t x1, int64_t y1, uint32_t c) {

  int64_t swap = x1;
  if (x1 < x0) {
    x1 = x0;
    x0 = swap;
  }
  swap = y1;
  if (y1 < y0) {
    y1 = y0;
    y0 = swap;
  }

  // Calculate radius
  int32_t rx = llabs(x1 - x0) / 2; // Radius on x
  int32_t ry = llabs(y1 - y0) / 2; // Radius on y
  int32_t rxSquare = rx*rx;
  int32_t rySquare = ry*ry;
  int32_t rx2ry2 = rxSquare * rySquare;

  // Start drawing at (0, ry)
  double x = 0;
  double y = ry;
  double d = 0;

  int32_t width = (rx + 1) * 2;
  int32_t height = (ry + 1) * 2;
  uint32_t* blitBuffer = ENGINE_resizeBlitBuffer(engine, width, height);
  size_t pitch = engine->blitBuffer.width;

  blitPixel(blitBuffer, pitch, rx + x, ry + y , c);
  blitPixel(blitBuffer, pitch, rx + x, ry - y , c);

  while (fabs(ellipse_getRegion(x, y, rx, ry)) < 1) {
    x++;
    double xSquare = x*x;
    // evaluate decision parameter
    d = rySquare * xSquare + rxSquare * pow(y - 0.5, 2) - rx2ry2;

    if (d > 0) {
      y--;
    }
    blitPixel(blitBuffer, pitch, rx + x, ry + y , c);
    blitPixel(blitBuffer, pitch, rx - x, ry - y , c);
    blitPixel(blitBuffer, pitch, rx - x, ry + y , c);
    blitPixel(blitBuffer, pitch, rx + x, ry - y , c);
  }

  while (y > 0) {
    y--;
    double ySquare = y*y;
    // evaluate decision parameter
    d = rxSquare * ySquare + rySquare * pow(x + 0.5, 2) - rx2ry2;

    if (d <= 0) {
      x++;
    }
    blitPixel(blitBuffer, pitch, rx + x, ry + y , c);
    blitPixel(blitBuffer, pitch, rx - x, ry - y , c);
    blitPixel(blitBuffer, pitch, rx - x, ry + y , c);
    blitPixel(blitBuffer, pitch, rx + x, ry - y , c);
  };
  ENGINE_blitBuffer(engine, x0, y0);
}

internal void
ENGINE_rect(ENGINE* engine, int64_t x, int64_t y, int64_t w, int64_t h, uint32_t c) {
  w = w - 1;
  h = h - 1;
  ENGINE_line(engine, x, y, x, y+h, c, 1);
  ENGINE_line(engine, x, y, x+w, y, c, 1);
  ENGINE_line(engine, x, y+h, x+w, y+h, c, 1);
  ENGINE_line(engine, x+w, y, x+w, y+h, c, 1);
}

internal void
ENGINE_rectfill(ENGINE* engine, int64_t x, int64_t y, int64_t w, int64_t h, uint32_t c) {
  uint16_t alpha = (0xFF000000 & c) >> 24;
  if (alpha == 0x00) {
    return;
  } else {
    int64_t y1 = y;
    int64_t y2 = y + h - 1;

    if (alpha == 0xFF) {
      size_t lineWidth = w; // x2 - x1;
      uint32_t buf[lineWidth + 1];
      for (size_t i = 0; i <= lineWidth; i++) {
        buf[i] = c;
      }
      for (int64_t j = y1; j <= y2; j++) {
        ENGINE_blitLine(engine, x, j, lineWidth, buf);
      }
    } else {
      int64_t x1 = x;
      int64_t x2 = x + w - 1;

      for (int64_t j = y1; j <= y2; j++) {
        for (int64_t i = x1; i <= x2; i++) {
          ENGINE_pset(engine, i, j, c);
        }
      }
    }
  }
}

internal bool
ENGINE_getKeyState(ENGINE* engine, char* keyName) {
  SDL_Keycode keycode =  SDL_GetKeyFromName(keyName);
  SDL_Scancode scancode = SDL_GetScancodeFromKey(keycode);
  const uint8_t* state = SDL_GetKeyboardState(NULL);
  return state[scancode];
}

internal void
ENGINE_setMouseRelative(ENGINE* engine, bool relative) {
  engine->mouse.relative = relative;
  if (relative) {
    SDL_SetRelativeMouseMode(SDL_TRUE);
    SDL_GetRelativeMouseState(&(engine->mouse.x), &(engine->mouse.y));
  } else {
    SDL_SetRelativeMouseMode(SDL_FALSE);
    SDL_GetMouseState(&(engine->mouse.x), &(engine->mouse.y));
  }
}

internal float
ENGINE_getMouseX(ENGINE* engine) {
  SDL_Rect viewport = engine->viewport;

  int mouseX = engine->mouse.x;
  int winX;
  int winY;
  SDL_GetWindowSize(engine->window, &winX, &winY);
  if (engine->mouse.relative) {
    return mouseX;
  } else {
    return mouseX * fmax((engine->canvas.width / (float)winX), engine->canvas.height / (float)winY) - viewport.x;
  }
}

internal float
ENGINE_getMouseY(ENGINE* engine) {
  SDL_Rect viewport = engine->viewport;

  int mouseY = engine->mouse.y;
  int winX;
  int winY;
  SDL_GetWindowSize(engine->window, &winX, &winY);
  if (engine->mouse.relative) {
    return mouseY;
  } else {
    return mouseY * fmax((engine->canvas.width / (float)winX), engine->canvas.height / (float)winY) - viewport.y;
  }
}

internal bool
ENGINE_getMouseButton(int button) {
  return SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(button);
}

internal void
ENGINE_drawDebug(ENGINE* engine) {
  char buffer[20];
  ENGINE_DEBUG* debug = &engine->debug;
  // Choose alpha depending on how fast or slow you want old averages to decay.
  // 0.9 is usually a good choice.
  double framesThisSecond = 1000.0 / (debug->elapsed+1);
  double alpha = debug->alpha;
  debug->avgFps = alpha * debug->avgFps + (1.0 - alpha) * framesThisSecond;
  snprintf(buffer, sizeof(buffer), "%.01f FPS", debug->avgFps);   // here 2 means binary
  int32_t width = engine->canvas.width;
  int32_t height = engine->canvas.height;
  int64_t startX = width - 8*8-2;
  int64_t startY = height - 8-2;

  ENGINE_rectfill(engine, startX, startY, 8*8+2, 10, 0x7F000000);
  ENGINE_print(engine, buffer, startX+1,startY+1, 0xFFFFFFFF);

  startX = width - 9*8 - 2;
  if (engine->vsyncEnabled) {
    ENGINE_print(engine, "VSync On", startX, startY - 8, 0xFFFFFFFF);
  } else {
    ENGINE_print(engine, "VSync Off", startX, startY - 8, 0xFFFFFFFF);
  }

  if (engine->lockstep) {
    ENGINE_print(engine, "Lockstep", startX, startY - 16, 0xFFFFFFFF);
  } else {
    ENGINE_print(engine, "Catchup", startX, startY - 16, 0xFFFFFFFF);
  }
}

internal bool
ENGINE_canvasResize(ENGINE* engine, uint32_t newWidth, uint32_t newHeight, uint32_t color) {
  if (engine->initialized && engine->record.makeGif) {
    return true;
  }
  if (engine->canvas.width == newWidth && engine->canvas.height == newHeight) {
    return true;
  }

  engine->canvas.width = newWidth;
  engine->canvas.height = newHeight;
  SDL_DestroyTexture(engine->texture);
  SDL_RenderSetLogicalSize(engine->renderer, newWidth, newHeight);

  engine->texture = SDL_CreateTexture(engine->renderer, SDL_PIXELFORMAT_ABGR8888, SDL_TEXTUREACCESS_STREAMING, newWidth, newHeight);
  if (engine->texture == NULL) {
    return false;
  }

  engine->canvas.pixels = realloc(engine->canvas.pixels, engine->canvas.width * engine->canvas.height * 4);
  if (engine->canvas.pixels == NULL) {
    return false;
  }
  ENGINE_rectfill(engine, 0, 0, engine->canvas.width, engine->canvas.height, color);
  SDL_RenderGetViewport(engine->renderer, &(engine->viewport));

  return true;
}

internal void
ENGINE_takeScreenshot(ENGINE* engine) {
  CANVAS canvas = engine->canvas;
  stbi_write_png("screenshot.png", canvas.width, canvas.height, 4, canvas.pixels, canvas.width * 4);
}


internal void
ENGINE_reportError(ENGINE* engine) {
  if (engine->debug.errorBuf != NULL) {
    ENGINE_printLog(engine, engine->debug.errorBuf);
    SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
                             "DOME - Error",
                             engine->debug.errorBuf,
                             NULL);
  }
}
<file_sep>/scripts/setup_wren.sh
#!/bin/bash
DOME_DIR=$PWD
INCLUDE_DIR=$DOME_DIR/include
LIB_DIR=$DOME_DIR/lib
WREN_DIR=$LIB_DIR/wren

cd "$WREN_DIR/projects"

if [[ "$OSTYPE" == "linux-gnu"* ]]; then
  # ...
  cd make
elif [[ "$OSTYPE" == "darwin"* ]]; then
  cd make.mac
  # Mac OSX
elif [[ "$OSTYPE" == "msys" ]]; then
  cd make
  # Lightweight shell and GNU utilities compiled for Windows (part of MinGW)
elif [[ "$OSTYPE" == "win32" ]]; then
  exit 1
else
  exit 1
fi

# Undo external makefile flags just in case
unset config
MAKEFLAGS="--no-print-directory"
# build the debug version of wren
make clean
CFLAGS=-fvisibility=hidden
make ${@:2} CFLAGS=${CFLAGS} verbose=1 config=debug_$1 wren && cp "$WREN_DIR/lib/libwren_d.a" "$LIB_DIR/libwrend.a"
# build the release version of wren
make clean
make ${@:2} CFLAGS=${CFLAGS} verbose=1 config=release_$1 wren && cp "$WREN_DIR/lib/libwren.a" "$LIB_DIR/libwren.a"
# Copy the wren.h to our includes
cp "$WREN_DIR/src/include/wren.h" "$INCLUDE_DIR/wren.h"
<file_sep>/include/vendor.c
#define _DEFAULT_SOURCE
#define NOMINMAX

#ifdef __MINGW32__
#include <windows.h>
#endif

#include <stdbool.h>
#include <jo_gif.h>

#define OPTPARSE_IMPLEMENTATION
#include <optparse.h>

#include <microtar/microtar.c>
#include <json/pdjson.c>
#include <mkdirp/mkdirp.c>

// Set up STB_IMAGE
#define STBI_FAILURE_USERMSG
#define STBI_NO_STDIO
#define STBI_ONLY_JPEG
#define STBI_ONLY_BMP
#define STBI_ONLY_PNG
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image_write.h>

#define STB_TRUETYPE_IMPLEMENTATION
#include <stb_truetype.h>

// Setup STB_VORBIS
#define STB_VORBIS_NO_PUSHDATA_API
#include <stb_vorbis.c>

// Setup ABC_FIFO
#include <SDL.h>
#define ABC_FIFO_IMPL
#include <ABC_fifo.h>
<file_sep>/docs/modules/random.md
[< Back](.)

random
=============

The `random` module provides utilities for generating pseudo-random numbers, for a variety of applications. Please note, this module should not be used for applications which require a cryptographically secure source of random numbers.

DOME's pseudo-random number generator is based on the "Squirrel3" noise function, described by [<NAME>](http://www.eiserloh.net/bio/) in [this talk](https://www.youtube.com/watch?v=LWFzPP8ZbdU).

## Random

### Static Methods

#### `noise(x: Number): Number`
Given `x` as an integer, this will return a 32-bit number based on the Squirrel3 noise function.

#### `noise(x: Number, seed: Number): Number`
Given `x` and `seed` as integers, this will return a 32-bit number based on the Squirrel3 noise function. The `seed` value can be used to get different outputs for the same position `x`.

### Instance methods
#### `construct new()`
Creates a new instance of a random number generator, seeded based on the current system time.

#### `construct new(seed: Number)`
Creates a new instance of a random number generator, based on the provided seed value.

#### `float(): Number`
Returns a floating point value in the range of `0.0...1.0`, inclusive of `0.0` but exclusive of `1.0`.

#### `float(end: Number): Number`
Returns a floating point value in the range of `0.0...end``, inclusive of `0.0` but exclusive of `end`.

#### `float(start: Number, end: Number): Number`
Returns a floating point value in the range of `start...end``, inclusive of `start` but exclusive of `end`.

#### `int(end: Number): Number`
Returns an integer in the range `0.0...end`, inclusive of `0.0` but exclusive of `end`.`
#### `int(start: Number, end: Number): Number`
Returns an integer in the range `start...end`, inclusive of `start` but exclusive of `end`.`

#### `sample(list: List): Any`
Given a `list`, this will pick an element from that list at random.

#### `sample(list: List, count: Number): List`
Randomly selects `count` elements from the list and returns them in a new list. This provides "sampling without replacement", so each element is distinct.

#### `shuffle(list: List): List`
Uses the Fisher-Yates algorithm to shuffle the provided `list` in place. The list is also returned for convenience.

<file_sep>/include/ABC_fifo.h
/*
  ABC_fifo.h - v0.0.12 - Public Domain
  Author: <NAME>, 2018

  How To Use:


  #define ABC_FIFO_IMPL
  #include "ABC_fifo.h"

  Version History:

  v0.0.12 - Moved the SDL warning so ABC_fifo can be compiled in a seperate translation unit.
  v0.0.11 - When closing, we actually make sure all the threads can wake up
            waiting for them all.
  v0.0.10 - We block when closing the FIFO til all threads finish
  v0.0.9 - Initial Release, be careful

  License:

  This is free and unencumbered software released into the public domain.

  Anyone is free to copy, modify, publish, use, compile, sell, or
  distribute this software, either in source code form or as a compiled
  binary, for any purpose, commercial or non-commercial, and by any
  means.

  In jurisdictions that recognize copyright laws, the author or authors
  of this software dedicate any and all copyright interest in the
  software to the public domain. We make this dedication for the benefit
  of the public at large and to the detriment of our heirs and
  successors. We intend this dedication to be an overt act of
  relinquishment in perpetuity of all present and future rights to this
  software under copyright law.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
  OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  OTHER DEALINGS IN THE SOFTWARE.

  For more information, please refer to <http://unlicense.org/>
*/


#ifndef ABC_FIFO_H
#define ABC_FIFO_H

// Initial configuration
#ifndef ABC_FIFO_POOL_SIZE
  #define ABC_FIFO_POOL_SIZE 4
#endif
#ifndef ABC_FIFO_SIZE
  #define ABC_FIFO_SIZE 256
#endif

typedef uint8_t ABC_TASK_TYPE;

typedef struct {
  int resultCode;
  ABC_TASK_TYPE type;
  void* data;
  void* returnData;
} ABC_TASK;

typedef int (*ABC_FIFO_TaskHandler)(ABC_TASK* task);
typedef struct {
  SDL_sem* semaphore;
  SDL_atomic_t readEntry;
  SDL_atomic_t completionCount;
  volatile uint16_t writeEntry;

  volatile bool shutdown;
  ABC_FIFO_TaskHandler taskHandler;
  SDL_Thread* threads[ABC_FIFO_POOL_SIZE];

  ABC_TASK tasks[ABC_FIFO_SIZE];
} ABC_FIFO;

// Public API:
void ABC_FIFO_create(ABC_FIFO* queue);
void ABC_FIFO_pushTask(ABC_FIFO* queue, ABC_TASK task);
bool ABC_FIFO_isFull(ABC_FIFO* queue);
bool ABC_FIFO_isEmpty(ABC_FIFO* queue);
void ABC_FIFO_waitForEmptyQueue(ABC_FIFO* queue);
void ABC_FIFO_close(ABC_FIFO* queue);

#endif // ABC_FIFO_ABC_FIFO_H




#ifdef SDL_INIT_EVERYTHING

#ifdef ABC_FIFO_IMPL
#define ABC_FIFO_IMPL


#ifndef SDL_INIT_EVERYTHING
#error "ABC_FIFO depends on SDL, which could not be detected."
#define SDL_sem void
#define SDL_atomic_t int
#define SDL_Thread int
#endif




int16_t mask(int16_t v) {
  return v & (ABC_FIFO_SIZE - 1);
}




int ABC_FIFO_executeTask(void* data);

// Initialise the queue passed in
void ABC_FIFO_create(ABC_FIFO* queue) {
  memset(queue, 0, sizeof(ABC_FIFO));
  queue->semaphore = SDL_CreateSemaphore(0);

  for (int i = 0; i < ABC_FIFO_POOL_SIZE; ++i) {
    queue->threads[i] = SDL_CreateThread(ABC_FIFO_executeTask, NULL, queue);
  }
}

// PRE: Only the main thread pushes tasks
// Blocks if the queue is full.
void ABC_FIFO_pushTask(ABC_FIFO* queue, ABC_TASK task) {
  while(ABC_FIFO_isFull(queue)) { };

  queue->tasks[mask(queue->writeEntry)] = task;
  SDL_CompilerBarrier();
  ++queue->writeEntry;

  if (SDL_SemValue(queue->semaphore) < ABC_FIFO_POOL_SIZE) {
    SDL_SemPost(queue->semaphore);
  }
}

int ABC_FIFO_executeTask(void* data) {
  ABC_FIFO* queue = (ABC_FIFO*)data;
  while (!queue->shutdown) {
   int16_t originalReadEntry = SDL_AtomicGet(&queue->readEntry);
   if (mask(originalReadEntry) != mask(queue->writeEntry)) {
     if (SDL_AtomicCAS(&queue->readEntry, originalReadEntry, originalReadEntry + 1)) {
       SDL_CompilerBarrier();
       ABC_TASK task = queue->tasks[mask(originalReadEntry)];

       queue->taskHandler(&task);
       SDL_AtomicAdd(&queue->completionCount, 1); // Post-increment
     }
    } else {
      SDL_SemWait(queue->semaphore);
    }
  }
  return 0;
}

// Shutdown pool and block til all threads close
void ABC_FIFO_close(ABC_FIFO* queue) {
  queue->shutdown = true;
  // Tidy up our resources
  for (int i = 0; i < ABC_FIFO_POOL_SIZE; ++i) {
    SDL_SemPost(queue->semaphore);
  }
  for (int i = 0; i < ABC_FIFO_POOL_SIZE; ++i) {
    SDL_WaitThread(queue->threads[i], NULL);
  }

  // Destroy queue
  SDL_DestroySemaphore(queue->semaphore);
}

bool ABC_FIFO_isFull(ABC_FIFO* queue) {
  return mask(queue->writeEntry + 1) == mask(SDL_AtomicGet(&queue->readEntry));
}

bool ABC_FIFO_isEmpty(ABC_FIFO* queue) {
  return mask(queue->writeEntry) == mask(SDL_AtomicGet(&queue->readEntry));
}

void ABC_FIFO_waitForEmptyQueue(ABC_FIFO* queue) {
  while (!ABC_FIFO_isEmpty(queue));
}

#endif // ABC_FIFO_ABC_FIFO_IMPL

#endif // SDL_INIT_EVERYTHING
#ifndef SDL_INIT_EVERYTHING
#undef SDL_sem
#undef SDL_atomic_t
#undef SDL_Thread
#endif

<file_sep>/src/modules/font.c
typedef struct {
  const unsigned char* file;
  stbtt_fontinfo info;
} FONT;

typedef struct {
  FONT* font;
  float scale;
  int height;

  bool antialias;
  int32_t offsetY;
} FONT_RASTER;

internal void
FONT_allocate(WrenVM* vm) {
  FONT* font = wrenSetSlotNewForeign(vm, 0, 0, sizeof(FONT));
  int size;
  // ASSERT
  ASSERT_SLOT_TYPE(vm, 1, STRING, "text");
  const unsigned char* file = (const unsigned char*) wrenGetSlotBytes(vm, 1, &size);
  char magic[4] = {0x00, 0x01, 0x00, 0x00};
  if (memcmp(file, magic, 4) != 0) {
    VM_ABORT(vm, "Given file is not a TTF file");
    return;
  }
  font->file = malloc(size * sizeof(char));
  memcpy((void*)font->file, file, size * sizeof(char));
  int result = stbtt_InitFont(&(font->info), font->file, stbtt_GetFontOffsetForIndex(file, 0));

  if (!result) {
    VM_ABORT(vm, "Loading font failed");
  }
}

internal void
FONT_finalize(void* data) {
  FONT* font = data;
  free((void*)font->file);
}

internal void
FONT_RASTER_allocate(WrenVM* vm) {
  FONT_RASTER* raster = wrenSetSlotNewForeign(vm, 0, 0, sizeof(FONT_RASTER));
  ASSERT_SLOT_TYPE(vm, 1, FOREIGN, "font");
  FONT* font = wrenGetSlotForeign(vm, 1);
  raster->font = font;
  raster->antialias = false;
  ASSERT_SLOT_TYPE(vm, 2, NUM, "font size");
  raster->scale = stbtt_ScaleForMappingEmToPixels(&font->info, wrenGetSlotDouble(vm, 2));

  int ascent, descent, linegap;
  stbtt_GetFontVMetrics(&font->info, &ascent, &descent, &linegap);
  raster->height = (ascent - descent + linegap) * (raster->scale);

  int32_t x0, x1, y0, y1;
  stbtt_GetFontBoundingBox(&font->info, &x0, &x1, &y0, &y1);
  raster->offsetY = (-y0) * raster->scale;
}

internal void
FONT_RASTER_finalize(void* data) {

}

internal void
FONT_RASTER_setAntiAlias(WrenVM* vm) {
  FONT_RASTER* raster = wrenGetSlotForeign(vm, 0);
  ASSERT_SLOT_TYPE(vm, 1, BOOL, "antialias");
  raster->antialias = wrenGetSlotBool(vm, 1);
}

internal void
FONT_RASTER_print(WrenVM* vm) {
  ENGINE* engine = wrenGetUserData(vm);
  FONT_RASTER* raster = wrenGetSlotForeign(vm, 0);
  stbtt_fontinfo info = raster->font->info;
  ASSERT_SLOT_TYPE(vm, 1, STRING, "text");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "x");
  ASSERT_SLOT_TYPE(vm, 3, NUM, "y");
  ASSERT_SLOT_TYPE(vm, 4, NUM, "color");
  const char* text = wrenGetSlotString(vm, 1);
  int64_t x = wrenGetSlotDouble(vm, 2);
  int64_t y = wrenGetSlotDouble(vm, 3);
  uint32_t color = wrenGetSlotDouble(vm, 4);

  unsigned char *bitmap;
  int w, h;

  int fontHeight = raster->height;

  float scale = raster->scale;
  int32_t offsetY = raster->offsetY;

  int32_t posX = x;
  int32_t posY = y;
  int32_t baseY = y - offsetY;
  int len = utf8len(text);
  utf8_int32_t codepoint;
  void* v = utf8codepoint(text, &codepoint);
  for (int charIndex = 0; charIndex < len; charIndex++) {
    if (text[charIndex] == '\n') {
      posX = x;
      baseY += fontHeight;
      v = utf8codepoint(v, &codepoint);
      continue;
    }
    int ax;
    int lsb;
    int oY, oX;
    stbtt_GetCodepointHMetrics(&info, codepoint, &ax, &lsb);
    bitmap = stbtt_GetCodepointBitmap(&info, 0, scale, codepoint, &w, &h, &oX, &oY);
    posX += oX;
    posY = baseY + oY;
    uint32_t outColor;
    float baseAlpha = ((color & 0xFF000000) >> 24) / (float)0xFF;

    for (int j = 0; j < h; j++) {
      for (int i = 0; i < w; i++) {
        if (raster->antialias) {
          uint8_t alpha = baseAlpha * bitmap[j * w + i];
          outColor = (alpha << 24) | (color & 0x00FFFFFF);
        } else {
          outColor = bitmap[j * w + i] > 0 ? color : 0;
        }
        ENGINE_pset(engine, posX + i, posY + j, outColor);
      }
    }
    posX += ax * scale;
    /* add kerning */
    int kern;
    long oldCodepoint = codepoint;
    v = utf8codepoint(v, &codepoint);
    kern = stbtt_GetCodepointKernAdvance(&info, oldCodepoint, codepoint);
    posX += kern * scale;
  }
}
<file_sep>/src/io.c
internal void FILESYSTEM_loadEventHandler(void* task);

global_variable char* basePath = NULL;

internal void
BASEPATH_set(char* path) {
  size_t len = strlen(path) + 1;
  bool slash = path[len - 1] == '/';
  if (!slash) {
    len++;
  }
  basePath = realloc(basePath, len * sizeof(char));
  strcpy(basePath, path);
  if (!slash) {
    basePath[len - 2] = '/';
  }
  basePath[len - 1] = '\0';
}

internal char*
BASEPATH_get(void) {
  if (basePath == NULL) {
    if (STRINGS_EQUAL(SDL_GetPlatform(), "Mac OS X")) {
      basePath = SDL_GetBasePath();
      if (strstr(basePath, ".app/") != NULL) {
        // If this is a MAC bundle, we need to use the exe location
        return basePath;
      } else {
        // Free the SDL path and fall back
        free(basePath);
      }
    }
    long path_max;
    size_t size;
    char *buf = NULL;
    char *ptr = NULL;
#ifdef __MINGW32__
    path_max = PATH_MAX;
#else
    path_max = pathconf(".", _PC_PATH_MAX);
#endif
    if (path_max == -1) {
      size = 1024;
    } else if (path_max > 10240) {
      size = 10240;
    } else {
      size = path_max;
    }

    for (buf = ptr = NULL; ptr == NULL; size *= 2) {
      if ((buf = realloc(buf, size + sizeof(char) * 2)) == NULL) {
        abort();
      }

      ptr = getcwd(buf, size);
      if (ptr == NULL && errno != ERANGE) {
        abort();
      }
    }
    basePath = ptr;
    size_t len = strlen(basePath);
    *(basePath + len) = '/';
    *(basePath + len + 1) = '\0';
    for (size_t i = 0; i < len + 2; i++) {
      if (basePath[i] == '\\') {
        basePath[i] = '/';
      }
    }
  }
  return basePath;
}

internal void
BASEPATH_free(void) {
  if (basePath != NULL) {
    free(basePath);
  }
}

internal const char*
resolvePath(const char* partialPath, bool* shouldFree) {
    const char* fullPath;
    if (partialPath[0] != '/') {
      char* base = BASEPATH_get();
      fullPath = malloc(strlen(base)+strlen(partialPath)+1);
      strcpy((char*)fullPath, base);
      strcat((char*)fullPath, partialPath);
      if (shouldFree != NULL) {
        *shouldFree = true;
      }
    } else {
      fullPath = partialPath;
      if (shouldFree != NULL) {
        *shouldFree = false;
      }
    }

    return fullPath;
}

internal inline bool
isDirectory(const char *path) {
   struct stat statbuf;
   if (stat(path, &statbuf) != 0) {
       return false;
   }
   return S_ISDIR(statbuf.st_mode) == 1;
}

internal inline bool
doesFileExist(char* path) {
  return access(path, F_OK) != -1;
}

internal int
readFileFromTar(mtar_t* tar, char* path, size_t* lengthPtr, char** data) {
  // We assume the tar open has been done already
  int err;
  mtar_header_t h;

  char compatiblePath[PATH_MAX];

  strcpy(compatiblePath, "./");
  strcat(compatiblePath, path);

  err = mtar_rewind(tar);
  if (err != MTAR_ESUCCESS) {
    return err;
  }

  while ((err = mtar_read_header(tar, &h)) == MTAR_ESUCCESS) {
    // search for "<path>", "./<path>" and "/<path>"
    // see https://github.com/domeengine/nest/pull/2
    if (!strcmp(h.name, path) ||
        !strcmp(h.name, compatiblePath) ||
        !strcmp(h.name, compatiblePath + 1)) {
      break;
    }
    err = mtar_next(tar);

    if (err != MTAR_ESUCCESS) {
      return err;
    }
  }

  if (err != MTAR_ESUCCESS) {
    return err;
  }

  size_t length = h.size;
  *data = calloc(1, length + 1);
  if ((err = mtar_read_data(tar, *data, length)) != MTAR_ESUCCESS) {
    // Some kind of problem reading the file
    free(*data);
    return err;
  }

  if (lengthPtr != NULL) {
    *lengthPtr = length;
  }

  return err;
}

internal int
writeEntireFile(const char* path, const char* data, size_t length) {
  FILE* file = fopen(path, "wb+");
  if (file == NULL) {
    return errno;
  }
  fwrite(data, sizeof(char), length, file);
  fclose(file);
  return 0;
}

internal char*
readEntireFile(char* path, size_t* lengthPtr) {
  FILE* file = fopen(path, "rb");
  if (file == NULL) {
    return NULL;
  }
  char* source = NULL;
  if (fseek(file, 0L, SEEK_END) == 0) {
    /* Get the size of the file. */
    long bufsize = ftell(file);
    /* Allocate our buffer to that size. */
    source = malloc(sizeof(char) * (bufsize + 1));

    /* Go back to the start of the file. */
    if (fseek(file, 0L, SEEK_SET) != 0) { /* Error */ }

    /* Read the entire file into memory. */
    size_t newLen = fread(source, sizeof(char), bufsize, file);
    if (ferror(file) != 0) {
      perror("Error reading file");
    } else {
      if (lengthPtr != NULL) {
        *lengthPtr = newLen;
      }
      source[newLen++] = '\0'; /* Just to be safe. */
    }
  }
  fclose(file);
  return source;
}


<file_sep>/scripts/set-executable-path.sh
#!/bin/bash
TARGET_NAME=$1
if [[ "$OSTYPE" == "darwin"* ]]; then
install_name_tool -add_rpath \@executable_path/libSDL2-2.0.0.dylib $TARGET_NAME
install_name_tool -change /usr/local/opt/sdl2/lib/libSDL2-2.0.0.dylib \@executable_path/libSDL2-2.0.0.dylib $TARGET_NAME
install_name_tool -change /usr/local/lib/libSDL2-2.0.0.dylib \@executable_path/libSDL2-2.0.0.dylib $TARGET_NAME
fi
<file_sep>/docs/plugins/index.md
[< Back](..)

Native Plugins
============


Advanced developers are invited to build native plugins using a compiled language like C, C++ and Rust. This allows for deeper system access than DOME's module API's expose, as well as greater performance. It also makes the features of various shared libraries available, at a small cost.

> Caution: The Native Plugin API is in an experimental phase and we may need to make some breaking changes in the future. Please keep an eye on this page for updates.

# Contents 

 * [Getting Started](#getting-started)
   - [Example](#example)
   - [Caveats](#caveats)
 * Lifecycle hooks
   - [Init](#init)
   - [Pre-Update](#pre-update)
   - [Post-Update](#post-update)
   - [Pre-Draw](#pre-draw)
   - [Post-Draw](#post-draw)
   - [Shutdown](#shutdown)
 * API Services
   - [Core](#core)
     * Enums
       - [enum: DOME_Result](#enum-dome_result)
     * Function Signatures
       - [function: DOME_ForeignFn](#function-dome_foreignfn)
       - [function: DOME_FinalizerFn](#function-dome_finalizerfn)
     * Methods
       - [method: registerModule](#method-registermodule)
       - [method: registerClass](#method-registerclass)
       - [method: registerFn](#method-registerfn)
       - [method: lockModule](#method-lockmodule)
       - [method: getContext](#method-getcontext)
       - [method: log](#method-log)
   - [Wren](#wren)
     * [Module Embedding](#module-embedding)
   - [Audio](#audio)
     * Enums
       - [enum: CHANNEL_STATE](#enum-channel_state)
     * Function Signatures
       - [function: CHANNEL_mix](#function-channel_mix)
       - [function: CHANNEL_callback](#function-channel_callback)
     * Methods
       - [method: channelCreate](#method-channelcreate)
       - [method: getData](#method-getdata)
       - [method: getState](#method-getstate)
       - [method: setState](#method-setstate)
       - [method: stop](#method-stop)


# Getting Started
In order to start writing your plugins, you will need to include `dome.h` in your project. This file can be found [here](https://github.com/domeengine/dome/blob/main/includes/dome.h) in the `includes` folder of the GitHub repository.
You will also need to configure your compiler/linker to ignore undefined methods and output a shared library. DOME supports plugins compiled as `.dll` (on Windows), `.so` (on Linux) and `.dylib` (on Mac OS X).

The compiled library has to be available in the shared library path, which varies by operating system convention, however usually it can be placed in the same folder as your application entry point. You should consult your operating system's developer documentation for more details.

You can load the plugin from your DOME application by calling [`Plugin.load(name)`](/modules/plugin)

## Example

You can find a well-commented example plugin and application on [this](example) page, which demonstrates all the currently available lifecycle hooks.

## Caveats 

Using plugins with DOME can hugely expand the functions of your application, but there are certain things to be aware of:

  1. Standard DOME applications are safely portable, as the engine is compiled for multiple platforms. This does not extend to plugins, which will need to be compiled for your target platforms and supplied with distributions of your application.
  2. DOME cannot verify the correctness of plugin implementations, which means that a plugin which has bugs could cause DOME to crash unexpectedly, or cause other issues with the underlying system.
  3. Your plugin will need to expose symbols with C-style function names. DOME cannot access functions whose names have been mangled.

# Plugin Interfaces

## Lifecycle hooks

DOME can call specially named functions implemented by your plugin, at different times during the game loop. For this to work, you must ensure that your compiler does not mangle names.

If a hook returns any result other than `DOME_RESULT_SUCCESS`, DOME will abort and shutdown. You should use the [`log(text)`](#method-log) call to print an error before returning.

### Init

```c
DOME_Result PLUGIN_onInit(DOME_getAPIFunction DOME_getAPI,
                          DOME_Context ctx)
```

DOME calls this function when the plugin is loaded, which gives you a chance to perform any initialisation you need to.
You can also signal to DOME that there was a problem by returning `DOME_RESULT_FAILURE`.

This is also the best opportunity to acquire the available APIs, thanks to the `DOME_getAPI` function pointer, which is explained in the [API Services](#api-services) section. The structs returned from this call should be stored for use throughout the lifetime of your plugin.

### Pre-Update
```c
DOME_Result PLUGIN_preUpdate(DOME_Context ctx)
```
This hook is called before the Game.update step of the game loop.

### Post-Update
```c
DOME_Result PLUGIN_postUpdate(DOME_Context ctx)
```
This hook is called after the Game.update step of the game loop.

### Pre-Draw
```c
DOME_Result PLUGIN_preDraw(DOME_Context ctx)
```

This hook is called before the Game.draw step of the game loop.

### Post-Draw
```c
DOME_Result PLUGIN_postDraw(DOME_Context ctx)
```
This hook is called after the Game.draw step of the game loop.


### Shutdown
```c
DOME_Result PLUGIN_onShutdown(DOME_Context ctx)
```
This hook occurs when the plugin is being unloaded, usually because DOME is in the process of quitting. This is your last opportunity to free any resources your plugin is holding on to, and cleaning up any other background processes.



# API Services

The DOME Plugin API is split into different pieces, divided by general purpose and version. This is to allow maximum backwards-compatibility as new features are added.
The engine will endeavour to support previous versions of an API for as long as possible, but no guarentees will be made for compatibility across major versions of DOME.

APIs are provided as a struct of function pointers, returned from:
```c
void* DOME_getAPI(API_TYPE type, int API_VERSION)
```

## Core

This API allows your plugin to register modules and provides some basic utilities.

### Acquisition

```c
DOME_API_v0* core = (DOME_API_v0*)DOME_getAPI(API_DOME, DOME_API_VERSION);
```

### Enums: 

#### enum: DOME_Result

Various methods return an enum of type `DOME_Result`, which indicates success or failure. These are the valid values:

 * `DOME_RESULT_SUCCESS`
 * `DOME_RESULT_FAILURE`
 * `DOME_RESULT_UNKNOWN`

### Function signatures

#### function: DOME_ForeignFn
`DOME_ForeignFn` methods have the signature: `void method(WrenVM* vm)` to match the `WrenForeignMethodFn` type.

#### function: DOME_FinalizerFn
`DOME_FinalizerFn` methods have the signature: `void finalize(void* vm)`, to match the `WrenFinalizerFn` type.

### Methods

#### method: registerModule
```c
DOME_Result registerModule(DOME_Context ctx, 
                           const char* name, 
                           const char* moduleSource)
```
This call registers module `name` with the source code `moduleSource`. You cannot register modules with the same name as DOME's internal modules. These are reserved. 
DOME creates a copy of the `name` and `moduleSource`, so you are able to free the pointers if necessary.
Returns `DOME_RESULT_SUCCESS` if the module was successfully registered, and `DOME_RESULT_FAILURE` otherwise.

#### method: registerClass
```c
DOME_Result registerClass(DOME_Context ctx, 
                          const char* moduleName, 
                          const char* className, 
                          DOME_ForeignFn allocate, 
                          DOME_FinalizerFn finalize)
```
Register the `allocate` and `finalize` methods for `className` in `moduleName`, so that instances of the foreign class can be allocated, and optionally finalized.
The `finalize` method is your final chance to deal with the userdata attached to your foreign class. You won't have VM access inside this method.
DOME creates a copy of the `className`, so you are able to free the pointer if necessary.

Returns `DOME_RESULT_SUCCESS` if the class is registered and `DOME_RESULT_FAILURE` otherwise. Failure will occur if `allocate` method is provided. The `finalize` argument can optionally be `NULL`.


#### method: registerFn
```c
DOME_Result registerFn(DOME_Context ctx, 
                       const char* name, 
                       const char* signature, 
                       DOME_ForeignFn method)
```
Register `method` as the function to call for the foreign method specified by `signature` in the module `name`. 
DOME creates a copy of the `signature`, so you are able to free the pointer if necessary.
Returns `DOME_RESULT_SUCCESS` if the function was successfully registered, and `DOME_RESULT_FAILURE` otherwise.

The format for the `signature` string is as follows:
 * `static` if the method is a static class method, followed by a space, otherwise both are omitted.
 * `ClassName` for the class method being declared, followed by a period (`.`)
 * `methodName` which is the name of the field/method being exposed.
   - If this is a field getter, nothing else is needed.
   - If this is a field setter, add `=(_)`
   - If this is a method, then parenthesis and a comma seperated list of underscores (`_`) follow, for the number of arguments the method takes. 
   - You can also use the setter and getter syntax for the class' subscript operator `[]`, which can be defined with one or more parameters.
   - Wren methods can have up to 16 arguments, and are overloaded by arity. For example, `Test.do(_)` is considered different to `Test.do(_,_)` and so on.
   
#### method: lockModule
```c
void lockModule(DOME_Context ctx, const char* name)
```
This marks the module `name` as locked, so that further functions cannot modify it. It is recommended to do this after you have registered all the methods for your module, however there is no requirement to.




#### method: getContext
```c
DOME_Context getContext(WrenVM* vm)
```
This allows foreign functions called by the Wren VM to access the current DOME context, to call various APIs.

#### method: log
```c
void log(DOME_Context ctx, const char* text, ...)
```

Using this method allows for formatted output of `text` to the various debugging outputs DOME uses (stdout, a debug console on Windows and a DOME-log.txt file). 

You can use C-style specifiers for the `text` string, as used in the `printf` family of functions.

## Wren

You have access to a subset of the [Wren slot API](https://wren.io/embedding/slots-and-handles.html) in order to access parameters and return values in foreign methods.
The methods are incredibly well documented in the [Wren public header](https://github.com/wren-lang/wren/blob/main/src/include/wren.h), so we will not be documenting the functions here.
 
You do not need to include the `wren.h` header in your application, as `dome.h` includes everything you need.

### Acquisition

```c
WREN_API_v0* wren = (WREN_API_v0*)DOME_getAPI(API_WREN, WREN_API_VERSION);
```

### Methods
This is a list of provided methods:
```c
      void     ensureSlots(WrenVM* vm, int slotCount);
      void     setSlotNull(WrenVM* vm, int slot);
      void     setSlotBool(WrenVM* vm, int slot, bool value);
      void     setSlotDouble(WrenVM* vm, int slot, double value);
      void     setSlotString(WrenVM* vm, int slot, const char* text);
      void     setSlotBytesWrenVM* vm, int slot, const char* data, size_t length);
      void*    setSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t length);
      bool     getSlotBool(WrenVM* vm, int slot);
      double   getSlotDouble(WrenVM* vm, int slot);    
const char*    getSlotString(WrenVM* vm, int slot);   
const char*    getSlotBytes(WrenVM* vm, int slot, int* length);                   

      WrenType getSlotType(WrenVM* vm, int slot);

      void     setSlotNewList(WrenVM* vm, int slot);
      int      getListCount(WrenVM* vm, int slot);
      void     getListElement(WrenVM* vm, int listSlot, int index, int elementSlot);
      void     setListElement(WrenVM* vm, int listSlot, int index, int elementSlot);
      void     insertInList(WrenVM* vm, int listSlot, int index, int elementSlot);

      void     setSlotNewMap(WrenVM* vm, int slot);
      int      getMapCount(WrenVM* vm, int slot);
      bool     getMapContainsKey(WrenVM* vm, int mapSlot, int keySlot);
      void     getMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot);
      void     setMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot);
      void     removeMapValue(WrenVM* vm, int mapSlot, int keySlot, int removedValueSlot);


WrenInterpretResult interpret(WrenVM* vm, const char* module, const char* source);
WrenInterpretResult call(WrenVM* vm, WrenHandle* method);

      bool     hasModule(WrenVM* vm, const char* module);
      bool     hasVariable(WrenVM* vm, const char* module, const char* name);
      void     getVariable(WrenVM* vm, const char* module, const char* name, int slot);
   WrenHandle* getSlotHandle(WrenVM* vm, int slot);
      void     setSlotHandle(WrenVM* vm, int slot, WrenHandle* handle);
      void     releaseHandle(WrenVM* vm, WrenHandle* handle);
      void     abortFiber(WrenVM* vm, int slot);
```

### Module Embedding

If your plugin registers a Wren module, you can embed the source of that module in your plugin by using DOME's built-in `--embed` command, which will convert it into a C include file.

```sh
$ dome -e | --embed   sourceFile [moduleVariableName] [destinationFile]
```

Example:

```sh
$ dome -e external.wren source external.wren.inc
```

This command will use `external.wren` to generate `external.wren.inc`, which contains the variable `sourceModule` for including in C/C++ source code.

## Audio

This set of APIs gives you access to DOME's audio engine, to provide your own audio channel implementations. You can use this to synthesize sounds, or play custom audio formats.

### Acquisition

```c
AUDIO_API_v0* wren = (AUDIO_API_v0*)DOME_getAPI(API_AUDIO, AUDIO_API_VERSION);
```

### Enums

#### enum: CHANNEL_STATE

Audio channels are enabled and disabled based on a state, which is represented by this enum. Supported states are the following:

```c
enum CHANNEL_STATE {
  CHANNEL_INITIALIZE,
  CHANNEL_TO_PLAY,
  CHANNEL_PLAYING,
  CHANNEL_STOPPING,
  CHANNEL_STOPPED
}
```

### Function Signatures

#### function: CHANNEL_mix
`CHANNEL_mix` functions have a signature of `void mix(CHANNEL_REF ref, float* buffer, size_t sampleRequestSize)`. 

  * `ref` is a reference to the channel being mixed. 
  * `buffer` is an interleaved stereo buffer to write your audio data into. One sample is two values, for left and right, so `buffer` is `2 * sampleRequestSize` in size. 

This callback is called on DOME's Audio Engine mixer thread. It is essential that you avoid any slow operations (memory allocation, network) or you risk interruptions to the audio playback.

#### function: CHANNEL_callback
`CHANNEL_callback` functions have this signature: `void callback(CHANNEL_REF ref, WrenVM* vm)`.


### Methods

#### method: channelCreate
```c
CHANNEL_REF channelCreate(DOME_Context ctx,
                          CHANNEL_mix mix, 
                          CHANNEL_callback update, 
                          CHANNEL_callback finish, 
                          void* userdata);
```

When you create a new audio channel, you must supply callbacks for mixing, updating and finalizing the channel. This allows it to play nicely within DOME's expected audio lifecycle.

This method creates a channel with the specified callbacks and returns its corresponding CHANNEL_REF value, which can be used to manipulate the channel's state during execution. The channel will be created in the state `CHANNEL_INITIALIZE`, which gives you the opportunity to set up the channel configuration before it is played.

The callbacks work like this:
  - `update` is called once a frame, and can be used for safely modifying the state of the channel data. This callback holds a lock over the mixer thread, so avoid holding it for too long.
  - `finish` is called once the channel has been set to `STOPPED`, before its memory is released. It is safe to expect that the channel will not be played again.

The `userdata` is a pointer set by the plugin developer, which can be used to pass through associated data, and retrieved by [`getData(ref)`](#method-getdata). You are responsible for the management of the memory pointed to by that pointer and should avoid modifying the contents of the memory outside of the provided callbacks.


#### method: getData
```c
void* getData(CHANNEL_REF ref)
```
Fetch the `userdata` pointer for the given channel `ref`.

#### method: getState
```c
CHANNEL_STATE getState(CHANNEL_REF ref)
```
Get the current [state](#enum-channel_state) of the channel specified by `ref`.

#### method: setState
```c 
void setState(CHANNEL_REF ref, CHANNEL_STATE state)
```
This allows you to specify the channel's [state](#enum-channel_state). DOME will only mix in channels in the following states: `CHANNEL_PLAYING` and `CHANNEL_STOPPING`.

#### method: stop
```c
void stop(CHANNEL_REF ref)
```
Marks the audio channel as having stopped. This means that DOME will no longer play this channel. It will call the `finish` callback at it's next opportunity.
<file_sep>/src/audio/engine.c
#define AUDIO_CHANNEL_START 0
#define SAMPLE_RATE 44100

global_variable const uint16_t channels = 2;
global_variable const uint16_t bytesPerSample = 4 * 2; // 4-byte float * 2 channels;

typedef struct AUDIO_ENGINE_t {
  SDL_AudioDeviceID deviceId;
  SDL_AudioSpec spec;
  float* scratchBuffer;
  size_t scratchBufferSize;

  TABLE pending;
  TABLE playing;
  CHANNEL_ID nextId;
} AUDIO_ENGINE;

// audio callback function
// Allows SDL to "pull" data into the output buffer
// on a seperate thread. We need to be pretty efficient
// here as it holds a lock.
void AUDIO_ENGINE_mix(void*  userdata,
    Uint8* stream,
    int    outputBufferSize) {
  AUDIO_ENGINE* audioEngine = userdata;

  size_t totalSamples = outputBufferSize / bytesPerSample;

  SDL_memset(stream, 0, outputBufferSize);

  float* scratchBuffer = audioEngine->scratchBuffer;
  size_t bufferSampleSize = audioEngine->scratchBufferSize;
  TABLE_ITERATOR iter;
  TABLE_iterInit(&iter);
  CHANNEL* channel;
  while (TABLE_iterate(&(audioEngine->playing), &iter)) {
    channel = iter.value;
    if (!CHANNEL_isPlaying(channel)) {
      continue;
    }

    assert(channel != NULL);
    size_t requestServed = 0;
    float* writeCursor = (float*)(stream);
    CHANNEL_mix mixFn = channel->mix;

    while (CHANNEL_isPlaying(channel) && requestServed < totalSamples) {
      SDL_memset(scratchBuffer, 0, bufferSampleSize * bytesPerSample);
      size_t requestSize = min(bufferSampleSize, totalSamples - requestServed);
      mixFn(channel->ref, scratchBuffer, requestSize);
      requestServed += requestSize;
      float* copyCursor = scratchBuffer;
      float* endPoint = copyCursor + bufferSampleSize * channels;
      for (; copyCursor < endPoint; copyCursor++) {
        *(writeCursor++) += *copyCursor;
      }
    }
  }
}

internal AUDIO_ENGINE*
AUDIO_ENGINE_init(void) {
  SDL_InitSubSystem(SDL_INIT_AUDIO);
  AUDIO_ENGINE* engine = malloc(sizeof(AUDIO_ENGINE));

  // zero is reserved for uninitialized.
  engine->nextId = 1;

  // SETUP player
  // set the callback function
  (engine->spec).freq = SAMPLE_RATE;
  (engine->spec).format = AUDIO_F32LSB;
  (engine->spec).channels = channels; // TODO: consider mono/stereo
  (engine->spec).samples = AUDIO_BUFFER_SIZE;
  (engine->spec).callback = AUDIO_ENGINE_mix;
  (engine->spec).userdata = engine;

  // open audio device
  engine->deviceId = SDL_OpenAudioDevice(NULL, 0, &(engine->spec), NULL, 0);
  // TODO: Handle if we can't get a device!

  engine->scratchBuffer = calloc(AUDIO_BUFFER_SIZE, bytesPerSample);
  if (engine->scratchBuffer != NULL) {
    engine->scratchBufferSize = AUDIO_BUFFER_SIZE;
  }

  // Unpause audio so we can begin taking over the buffer
  SDL_PauseAudioDevice(engine->deviceId, 0);

  TABLE_init(&(engine->pending));
  TABLE_init(&(engine->playing));

  return engine;
}

internal bool
AUDIO_ENGINE_get(AUDIO_ENGINE* engine, CHANNEL_REF* ref, CHANNEL** channel) {
  CHANNEL_ID id = ref->id;
  if (id == 0) {
    return false;
  }
  bool result = TABLE_get(&engine->playing, id, channel);
  if (!result) {
    result = TABLE_get(&engine->pending, id, channel);
  }
  return result;
}

internal void
AUDIO_ENGINE_lock(AUDIO_ENGINE* engine) {
  SDL_LockAudioDevice(engine->deviceId);
}

internal void
AUDIO_ENGINE_unlock(AUDIO_ENGINE* engine) {
  SDL_UnlockAudioDevice(engine->deviceId);
}

internal CHANNEL_REF
AUDIO_ENGINE_channelInit(
    AUDIO_ENGINE* engine,
    CHANNEL_mix mix,
    CHANNEL_callback update,
    CHANNEL_callback finish,
    void* userdata) {

  CHANNEL_ID id = engine->nextId++;
  CHANNEL_REF ref = {
    .id = id,
    .engine = engine
  };
  CHANNEL channel = {
    .state = CHANNEL_INITIALIZE,
    .mix = mix,
    .update = update,
    .finish = finish,
    .userdata = userdata,
    .ref = ref
  };
  TABLE_set(&engine->pending, id, channel);

  return ref;
}

internal void
AUDIO_ENGINE_update(AUDIO_ENGINE* engine, WrenVM* vm) {
  TABLE_ITERATOR iter;
  TABLE_iterInit(&iter);
  CHANNEL* channel;
  AUDIO_ENGINE_lock(engine);
  TABLE_addAll(&engine->playing, &engine->pending);
  while (TABLE_iterate(&(engine->playing), &iter)) {
    channel = iter.value;
    if (channel->update != NULL) {
      channel->update(channel->ref, vm);
    }
    if (channel->state == CHANNEL_STOPPED) {
      if (channel->finish != NULL) {
        channel->finish(channel->ref, vm);
      }
      TABLE_delete(&engine->playing, channel->ref.id);
    }
  }
  AUDIO_ENGINE_unlock(engine);
  TABLE_free(&engine->pending);
  //  DEBUG_LOG("Capacity: %u / %u", engine->playing.items, (engine->playing).capacity);
}

internal void
AUDIO_ENGINE_stop(AUDIO_ENGINE* engine, CHANNEL_REF* ref) {
  CHANNEL* channel;
  AUDIO_ENGINE_get(engine, ref, &channel);
  if (channel != NULL) {
    CHANNEL_requestStop(channel);
  }
}

internal void*
AUDIO_ENGINE_getData(AUDIO_ENGINE* engine, CHANNEL_REF* ref) {
  CHANNEL* channel;
  AUDIO_ENGINE_get(engine, ref, &channel);
  if (channel != NULL) {
    return CHANNEL_getData(channel);
  }
  return NULL;
}

internal void
AUDIO_ENGINE_stopAll(AUDIO_ENGINE* engine) {
  TABLE_ITERATOR iter;
  TABLE_iterInit(&iter);
  CHANNEL* channel;
  while (TABLE_iterate(&(engine->playing), &iter)) {
    channel = iter.value;
    CHANNEL_requestStop(channel);
  }
  TABLE_iterInit(&iter);
  while (TABLE_iterate(&(engine->pending), &iter)) {
    channel = iter.value;
    CHANNEL_requestStop(channel);
  }
}


internal void
AUDIO_ENGINE_pause(AUDIO_ENGINE* engine) {
  SDL_PauseAudioDevice(engine->deviceId, 1);
}

internal void
AUDIO_ENGINE_resume(AUDIO_ENGINE* engine) {
  SDL_PauseAudioDevice(engine->deviceId, 0);
}

internal void
AUDIO_ENGINE_halt(AUDIO_ENGINE* engine) {
  if (engine != NULL) {
    SDL_PauseAudioDevice(engine->deviceId, 1);
    SDL_CloseAudioDevice(engine->deviceId);
  }
}

internal void
AUDIO_ENGINE_free(AUDIO_ENGINE* engine) {
  // We might need to free contained audio here
  AUDIO_ENGINE_halt(engine);
  free(engine->scratchBuffer);
  TABLE_free(&engine->playing);
  TABLE_free(&engine->pending);
}

<file_sep>/src/audio/api.c
internal CHANNEL_REF
AUDIO_API_channelCreate(
    DOME_Context ctx,
    CHANNEL_mix mix,
    CHANNEL_callback update,
    CHANNEL_callback finish,
    void* userdata) {

  AUDIO_ENGINE* engine = ((ENGINE*)ctx)->audioEngine;
  return AUDIO_ENGINE_channelInit(
      engine,
      mix,
      update,
      finish,
      userdata
  );
 }

internal void
AUDIO_API_stop(CHANNEL_REF ref) {
  AUDIO_ENGINE* engine = ref.engine;
  AUDIO_ENGINE_stop(engine, &ref);
}


internal void
AUDIO_API_setState(CHANNEL_REF ref, CHANNEL_STATE state) {
  AUDIO_ENGINE* engine = ref.engine;
  AUDIO_ENGINE_setState(engine, &ref, state);
}
internal CHANNEL_STATE
AUDIO_API_getState(CHANNEL_REF ref) {
  AUDIO_ENGINE* engine = ref.engine;
  return AUDIO_ENGINE_getState(engine, &ref);
}

internal void*
AUDIO_API_getData(CHANNEL_REF ref) {
  AUDIO_ENGINE* engine = ref.engine;
  return AUDIO_ENGINE_getData(engine, &ref);
}


AUDIO_API_v0 audio_v0 = {
  .channelCreate = AUDIO_API_channelCreate,
  .getState = AUDIO_API_getState,
  .setState = AUDIO_API_setState,
  .stop = AUDIO_API_stop,
  .getData = AUDIO_API_getData
};
<file_sep>/src/modules/platform.c
internal void
PLATFORM_getTime(WrenVM* vm) {
  wrenEnsureSlots(vm, 1);
  wrenSetSlotDouble(vm, 0, (uint32_t)time(NULL));
}

internal void
PLATFORM_getName(WrenVM* vm) {
  wrenEnsureSlots(vm, 1);
  wrenSetSlotString(vm, 0, SDL_GetPlatform());
}
<file_sep>/docs/modules/math.md
[< Back](.)

math
=============

The `math` module provides utilities for performing mathematical calculations.

It contains the following classes:

* [Math](#math)
* [Vector](#vector)

## Math

The `Math` class provides various common mathematical functions. For convenience, you can also import this class `M`, like so:

```wren
import "math" for M, Math
System.print(M.min(21, 42))
System.print(Math.max(21, 42))
```

### Static Methods

#### `abs(n: Number): Number`
Returns the absolute magnitude of `n`.

#### `acos(n: Number): Number`
Returns the arccosine of `n`.

#### `asin(n: Number): Number`
Returns the arcsine of `n`.

#### `atan(n: Number): Number`
#### `atan(n1: Number, n2: Number): Number`
Returns the arctan of `n`.

#### `ceil(n: Number): Number`
Rounds `n` up to the next largest integer value.

#### `cos(n: Number): Number`
Returns the cosine of `n`.

#### `floor(n: Number): Number`
Rounds `n` down to the next smallest integer value.

#### `lerp(low: Number, value: Number, high: Number): Number`
This performs a linear interpolation between `low` and `high`, based on the value of `value`, which will be clamped to a range of [0..1].

#### `log(n: Number): Number`
Returns the natural logarithm of `n`.

#### `max(n1: Number, n2: Number): Number`
Returns the larger of `n1` and `n2`.

#### `mid(n1: Number, n2: Number, n3: Number): Number`
Returns the number which is in the middle of the others. This can be used as an inclusive clamping function.

#### `min(n1: Number, n2: Number): Number`
Returns the smaller of `n1` and `n2`.

#### `round(n: Number): Number`
Rounds `n` to the nearest integer value.

#### `sign(n: Number): Number`
If `n` is negative, this returns `-1`. If `n` is positive, this returns `1`. If `n` is equal to zero, it returns `0`.

#### `sin(n: Number): Number`
Returns the sine of `n`.

#### `tan(n: Number): Number`
Returns the tan of `n`.

## Vector

The `Vector` class works as a vector of up to 4 dimensions. You can also refer to it as a `Point` or `Vec`.

### Constructor

#### `Vector.new(): Vector`
#### `Vector.new(x, y): Vector`
#### `Vector.new(x, y, z): Vector`
#### `Vector.new(x, y, z, w): Vector`

Create a vector. If a value isn't provided, it is set to `(0, 0, 0, 0)`.
Unless you specifically need 3 or 4-dimensional vectors, you can ignore _z_ and _w_.

### Instance Fields
#### `x: Number`
#### `y: Number`
#### `z: Number`
#### `w: Number`

#### `manhattan: Number`
This returns the "taxicab length" of the vector, easily calculated as `x` + `y` + `z` + `w`. 

#### `length: Number`
This returns the Euclidean magnitude of the vector.

#### `perp: Vector`
Returns the 2D vector perpendicular to the current vector. This doesn't work for vectors with 3 or more dimensions. 

#### `unit: Vector`
Returns a copy of the current vector, where it's arguments have been scaled such that it's length is 1.


### Instance Methods

#### `dot(vec: Vector): Num`
This returns the dot-product of the vector with another vector.
#### `cross(vec: Vector): Num`
This returns the cross-product of the vector with another vector, resulting in a new vector that is perpendicular to the other two. This only works for 3-dimensional vectors. The _w_ component will be discarded.


### Operators
#### `-Vector: Vector`
Returns the vector, but it's elements are multiplied by -1.

#### `Vector + Vector: Vector`
Returns an element-wise addition of the two Vectors. This will error if you try to add a Vector to something other than a Vector.

#### `Vector - Vector: Vector`
Returns an element-wise subtraction of the two Vectors. This will error if you try to subtract a Vector from something other than a Vector, or vice versa.

#### `Vector * Number: Vector`
Returns the given vector, where it's elements have been multipled by the scalar. This will error if the multiplier is not a number.

#### `Vector / Number: Vector`
Returns the given vector, where it's elements have been divided by the scalar. This will error if the divisor is not a Number.
<file_sep>/docs/modules/dome.md
[< Back](.)

# dome

The `dome` module allows you to control various aspects of how DOME as an application operates.

It contains the following classes:

- [Platform](#platform)
- [Process](#process)
- [Version](#version)
- [Window](#window)

## Platform

Contains platform-specific utilities which may not always be supported.

### Static Fields

#### `name: String`
Returns the generic name of the operating system or platform, where possible.

#### `time: Number`
Returns the integer number of seconds since the unix epoch (1st January 1970).

## Process

### Static Fields
#### `static args: String[]`
Returns a string list of all non-option command line arguments used to invoke DOME's execution. The first two elements of the returned list will be:

1. the path and name of DOME's invokation.
2. The entry path, combined with the evaluated basepath.

### Static Methods

#### `static exit(): Void`

#### `static exit(code: Number): Void`

Allows you to programmatically close DOME. This command behaves a little differently based on whether an error code is provided (defaults to `0`):

- If `code` is `0`, then this will immediately shutdown DOME in a graceful manner, but no other Wren code will execute after this call.
- Otherwise, the current Fiber will be aborted, the game loop will exit and DOME will shutdown.

## Version
This class provides information about the version of DOME which is currently running. You can use this to check that all the features you require are supported.
DOME uses semantic versioning, split into a major.minor.patch breakdown.

### Static Fields
#### `static major: Number`
The major component of the version number.
#### `static minor: Number`
The minor component of the version number.
#### `static patch: Number`
The patch component of the version number.
#### `static toString: String`
A string containing the complete semantic version of the DOME instance running.

#### `static toList: List<Number>`
A list of the version components, in `[major, minor, patch]` order.

### Static Methods
#### `static atLeast(version: String): boolean`
This takes a version as a string of the form `x.y.z`, and returns true if the current version of DOME is at least that of the version specified.


## Window

### Static Fields

#### `static fps: Number`

This is the current frames per second number.

#### `static fullscreen: Boolean`

Set this to switch between Windowed and Fullscreen modes.

#### `static height: Number`

This is the height of the window/viewport, in pixels.

#### `static lockstep: Boolean`

Setting this to true will disable "catch up" game loop behaviour. This is useful for lighter games, or on systems where you experience a little stuttering at 60fps.

#### `static title: String`

This allows you to set and get the title of the DOME window.

#### `static vsync: Boolean`

Setting this to true will make the renderer wait for VSync before presenting to the display. Changing this value is an expensive operation and so shouldn't be done frequently.

#### `static width: Number`

This is the width of the window/viewport, in pixels.

#### `static color: Color`

This is the background color of the window. Note that to this _does not_ affect inside the drawing canvas: use `Canvas.cls()` to clear the canvas to some color. This field only affects the background color beyond canvas boundaries. Default is black. Cannot be transparent (transparency is ignored).

### Static Methods

#### `static resize(width: Number, height: Number): Void`

This allows you to control the size of DOME's window. The viewport will scale accordingly, but the canvas will NOT resize.
<file_sep>/src/plugin.h
#ifndef PLUGIN_H
#define PLUGIN_H

struct ENGINE_t;

typedef DOME_Result (*DOME_Plugin_Init_Hook) (DOME_getAPIFunction apiFn, DOME_Context context);
typedef DOME_Result (*DOME_foreignFn) (DOME_Context context, WrenVM* vm);

typedef struct {
  // Total allocated size
  size_t max;
  // Number in use
  size_t count;
  bool* active;
  const char** name;
  void** objectHandle;
  DOME_Plugin_Hook* preUpdateHook;
  DOME_Plugin_Hook* postUpdateHook;
  DOME_Plugin_Hook* preDrawHook;
  DOME_Plugin_Hook* postDrawHook;
} PLUGIN_COLLECTION;

typedef enum {
  DOME_PLUGIN_HOOK_INIT,
  DOME_PLUGIN_HOOK_SHUTDOWN,
  DOME_PLUGIN_HOOK_PRE_UPDATE,
  DOME_PLUGIN_HOOK_POST_UPDATE,
  DOME_PLUGIN_HOOK_PRE_DRAW,
  DOME_PLUGIN_HOOK_POST_DRAW,
  DOME_PLUGIN_HOOK_UNKNOWN
} DOME_PLUGIN_HOOK;

internal void PLUGIN_COLLECTION_init(struct ENGINE_t* engine);
internal void PLUGIN_COLLECTION_free(struct ENGINE_t* engine);

internal DOME_Result PLUGIN_COLLECTION_runHook(struct ENGINE_t* engine, DOME_PLUGIN_HOOK hook);
internal DOME_Result PLUGIN_COLLECTION_add(struct ENGINE_t* engine, const char* name);

#endif
<file_sep>/src/audio/channel.c
internal inline void*
CHANNEL_getData(CHANNEL* channel) {
  assert(channel != NULL);
  return channel->userdata;
}

internal inline void
CHANNEL_setState(CHANNEL* channel, CHANNEL_STATE state) {
  assert(channel != NULL);
  channel->state = state;
}

internal inline CHANNEL_STATE
CHANNEL_getState(CHANNEL* channel) {
  assert(channel != NULL);
  return channel->state;
}

internal inline void
CHANNEL_requestStop(CHANNEL* channel) {
  assert(channel != NULL);
  channel->stopRequested = true;
}

internal inline bool
CHANNEL_isPlaying(CHANNEL* channel) {
  assert(channel != NULL);
  return channel->state == CHANNEL_PLAYING
    || channel->state == CHANNEL_STOPPING
    || channel->state == CHANNEL_VIRTUALIZING;
}

internal inline bool
CHANNEL_hasStopRequested(CHANNEL* channel) {
  assert(channel != NULL);
  return channel->stopRequested;
}

internal void
AUDIO_ENGINE_setPosition(AUDIO_ENGINE* engine, CHANNEL_REF* ref, size_t position) {
  CHANNEL* base;
  if (!AUDIO_ENGINE_get(engine, ref, &base)) {
    return;
  }
  AUDIO_CHANNEL* channel = (AUDIO_CHANNEL*)CHANNEL_getData(base);
  channel->new.position = mid(0, position, channel->audio->length);
  channel->new.resetPosition = true;
}

internal void
AUDIO_ENGINE_setState(AUDIO_ENGINE* engine, CHANNEL_REF* ref, CHANNEL_STATE state) {
  CHANNEL* base;
  if (!AUDIO_ENGINE_get(engine, ref, &base)) {
    return;
  }
  CHANNEL_setState(base, state);
}

internal CHANNEL_STATE
AUDIO_ENGINE_getState(AUDIO_ENGINE* engine, CHANNEL_REF* ref) {
  CHANNEL* base;
  if (!AUDIO_ENGINE_get(engine, ref, &base)) {
    return CHANNEL_STOPPED;
  }
  return CHANNEL_getState(base);
}


#define AUDIO_CHANNEL_GETTER(fieldName, method, fieldType, defaultValue) \
  internal fieldType \
  AUDIO_ENGINE_get##method(AUDIO_ENGINE* engine, CHANNEL_REF* ref) { \
    CHANNEL* base; \
    if (!AUDIO_ENGINE_get(engine, ref, &base)) { \
      return defaultValue; \
    } \
    AUDIO_CHANNEL* channel = (AUDIO_CHANNEL*)CHANNEL_getData(base); \
    return channel->fieldName; \
  }

#define AUDIO_CHANNEL_SETTER(fieldName, method, fieldType) \
  internal void \
  AUDIO_ENGINE_set##method(AUDIO_ENGINE* engine, CHANNEL_REF* ref, fieldType value) { \
    CHANNEL* base; \
    if (!AUDIO_ENGINE_get(engine, ref, &base)) { \
      return; \
    } \
    AUDIO_CHANNEL* channel = (AUDIO_CHANNEL*)CHANNEL_getData(base); \
    channel->fieldName = value; \
  }
#define AUDIO_CHANNEL_SETTER_WITH_RANGE(fieldName, method, fieldType, min, max) \
  internal void \
  AUDIO_ENGINE_set##method(AUDIO_ENGINE* engine, CHANNEL_REF* ref, fieldType value) { \
    CHANNEL* base; \
    if (!AUDIO_ENGINE_get(engine, ref, &base)) { \
      return; \
    } \
    AUDIO_CHANNEL* channel = (AUDIO_CHANNEL*)CHANNEL_getData(base); \
    channel->fieldName = fmid(min, value, max); \
  }

// volume
AUDIO_CHANNEL_SETTER_WITH_RANGE(new.volume, Volume, float, 0.0f, 1.0f)
AUDIO_CHANNEL_GETTER(new.volume, Volume, float, 0.0f)
// pan
AUDIO_CHANNEL_SETTER_WITH_RANGE(new.pan, Pan, float, -1.0f, 1.0f)
AUDIO_CHANNEL_GETTER(new.pan, Pan, float, 0.0f)

// loop
AUDIO_CHANNEL_SETTER(new.loop, Loop, bool)
AUDIO_CHANNEL_GETTER(new.loop, Loop, bool, false)
// position
AUDIO_CHANNEL_GETTER(new.position, Position, size_t, 0)


internal void
AUDIO_CHANNEL_finish(CHANNEL_REF ref, WrenVM* vm) {
  CHANNEL* base;
  if (!AUDIO_ENGINE_get(ref.engine, &ref, &base)) {
    return;
  }
  AUDIO_CHANNEL* channel = (AUDIO_CHANNEL*)CHANNEL_getData(base);
  assert(channel != NULL);
  if (channel->audioHandle != NULL) {
    wrenReleaseHandle(vm, channel->audioHandle);
    channel->audioHandle = NULL;
  }
  free(channel->soundId);
  free(channel);
}

internal void
AUDIO_CHANNEL_commit(AUDIO_CHANNEL* channel) {
  size_t position = channel->current.position;
  if (channel->new.resetPosition) {
    position = channel->new.position;
    channel->new.resetPosition = false;
  }
  channel->current = channel->new;
  channel->current.position = position;
  channel->new = channel->current;
}

internal void
AUDIO_CHANNEL_update(CHANNEL_REF ref, WrenVM* vm) {
  CHANNEL* base;
  if (!AUDIO_ENGINE_get(ref.engine, &ref, &base)) {
    return;
  }
  AUDIO_CHANNEL* channel = (AUDIO_CHANNEL*)CHANNEL_getData(base);
  switch (CHANNEL_getState(base)) {
    case CHANNEL_INITIALIZE:
      CHANNEL_setState(base, CHANNEL_TO_PLAY);
      // Fallthrough
    case CHANNEL_DEVIRTUALIZE:
    case CHANNEL_TO_PLAY:
      if (CHANNEL_getState(base) == CHANNEL_DEVIRTUALIZE) {
        // We might do special things to de-virtualize a channel
      }
      if (channel->audio == NULL) {
        CHANNEL_setState(base, CHANNEL_LOADING);
        break;
      }
      // We assume data is loaded by now.
      CHANNEL_setState(base, CHANNEL_PLAYING);
      AUDIO_CHANNEL_commit(channel);
      break;
    case CHANNEL_LOADING:
      if (channel->audio != NULL) {
        CHANNEL_setState(base, CHANNEL_TO_PLAY);
      }
      break;
    case CHANNEL_PLAYING:
      AUDIO_CHANNEL_commit(channel);
      if (CHANNEL_isPlaying(base) == false || CHANNEL_hasStopRequested(base)) {
        CHANNEL_setState(base, CHANNEL_STOPPING);
      }
      break;
    case CHANNEL_STOPPING:
      if (channel->fade) {
        channel->new.volume -= 0.1;
      } else {
        channel->new.volume = 0;
      }
      if (channel->new.volume <= 0) {
        channel->new.volume = 0;
        CHANNEL_setState(base, CHANNEL_STOPPED);
      }
      AUDIO_CHANNEL_commit(channel);
      break;
    case CHANNEL_STOPPED:
      AUDIO_CHANNEL_commit(channel);
      break;
    default: break;
  }
}

internal void
AUDIO_CHANNEL_mix(CHANNEL_REF ref, float* stream, size_t totalSamples) {
  CHANNEL* base;
  if (!AUDIO_ENGINE_get(ref.engine, &ref, &base)) {
    return;
  }
  AUDIO_CHANNEL* channel = (AUDIO_CHANNEL*)CHANNEL_getData(base);
  if (channel->audio == NULL) {
    return;
  }
  AUDIO_DATA* audio = channel->audio;
  float* startReadCursor = (float*)(audio->buffer);
  float* readCursor = startReadCursor + channel->current.position * channels;
  float* writeCursor = stream;
  size_t length = audio->length;

  size_t samplesToWrite = channel->current.loop ? totalSamples : min(totalSamples, length - channel->current.position);
  float volume = channel->current.volume;
  float targetPan = channel->current.pan;
  float actualVolume = channel->actualVolume;
  float actualPan = channel->actualPan;

  bool blendVolume = actualVolume != volume;
  bool blendPan = actualPan != targetPan;


  for (size_t i = 0; i < samplesToWrite; i++) {
    // We have to lerp the volume and pan change across the whole sample buffer
    // or we get a clicking sound.
    float f = i / (float)samplesToWrite;
    float currentVolume = actualVolume;
    if (blendVolume) {
      currentVolume = lerp(actualVolume, volume, f);
    }
    float currentPan = actualPan;
    if (blendPan) {
      currentPan = lerp(actualPan, targetPan, f);
    }
    float pan = (currentPan + 1.0f) * M_PI / 4.0f; // Channel pan is [-1,1] real pan needs to be [0,1]

    // We have to advance the cursor after each read and write
    // Read/Write left
    *(writeCursor++) = *(readCursor++) * cos(pan) * currentVolume;
    // Read/Write right
    *(writeCursor++) = *(readCursor++) * sin(pan) * currentVolume;

    channel->current.position++;
    if (channel->current.position >= length) {
      if (channel->current.loop) {
        channel->current.position = 0;
        readCursor = startReadCursor;
      } else {
        break;
      }
    }
  }
  channel->actualVolume = channel->current.volume;
  channel->actualPan = channel->current.pan;
  if (!channel->current.loop && channel->current.position >= length) {
    CHANNEL_setState(base, CHANNEL_STOPPED);
  }
}
internal float*
resample(float* data, size_t srcLength, uint64_t srcFrequency, uint64_t targetFrequency, size_t* destLength) {
  // Compute GCD of both frequencies
  uint64_t divisor = gcd(srcFrequency, targetFrequency);

  uint64_t L = targetFrequency / divisor;
  uint64_t M = srcFrequency / divisor;

  size_t sampleCount = srcLength;

  size_t tempSampleCount = sampleCount * L;
  float* tempData = calloc(tempSampleCount, bytesPerSample);
  if (tempData == NULL) {
    return NULL;
  }

  size_t destSampleCount = ceil(tempSampleCount / M) + 1;
  *destLength = destSampleCount;
  float* destData = malloc(destSampleCount * bytesPerSample);
  if (destData == NULL) {
    return NULL;
  }
  // Space out samples in temp data
  float* sampleCursor = data;
  float* writeCursor = tempData;
  for (size_t i = 0; i < sampleCount * L; i++) {
    size_t index = channels * (i / L);
    *(writeCursor++) = sampleCursor[index];
    *(writeCursor++) = sampleCursor[index + 1];
  }

  // TODO: Low-pass filter over the data (optional - but recommended)

  // decimate by M
  sampleCursor = tempData;
  writeCursor = destData;

  for(size_t i = 0; i < tempSampleCount; i += M) {
    *(writeCursor++) = sampleCursor[i*2];
    *(writeCursor++) = sampleCursor[i*2+1];
  }

  free(tempData);
  return destData;
}

internal CHANNEL_REF
AUDIO_CHANNEL_new(AUDIO_ENGINE* engine, const char* soundId) {

  AUDIO_CHANNEL* data = malloc(sizeof(AUDIO_CHANNEL));
  data->soundId = strdup(soundId);
  struct AUDIO_CHANNEL_PROPS props = {0, 0, 0, 0, 0};
  data->current = data->new = props;
  data->actualVolume = 0.0f;
  data->actualPan = 0.0f;
  data->audio = NULL;

  CHANNEL_REF ref = AUDIO_ENGINE_channelInit(
    engine,
    AUDIO_CHANNEL_mix,
    AUDIO_CHANNEL_update,
    AUDIO_CHANNEL_finish,
    data
  );
  return ref;
}

<file_sep>/src/modules/map.c
#include "modules.inc"

typedef struct CLASS_NODE {
  const char* name;
  WrenForeignClassMethods methods;
  struct CLASS_NODE* next;
} CLASS_NODE;

typedef struct FUNCTION_NODE {
  const char* signature;
  WrenForeignMethodFn fn;
  struct FUNCTION_NODE* next;
} FUNCTION_NODE;

typedef struct MODULE_NODE {
  const char* name;
  const char* source;
  bool locked;
  struct CLASS_NODE* classes;
  struct FUNCTION_NODE* functions;
  struct MODULE_NODE* next;
} MODULE_NODE;

typedef struct {
  MODULE_NODE* head;
} MAP;

internal MODULE_NODE* MAP_getModule(MAP* map, const char* name);

internal bool
MAP_addModule(MAP* map, const char* name, const char* source) {

  if (MAP_getModule(map, name) != NULL) {
    return false;
  }

  MODULE_NODE* newNode = malloc(sizeof(MODULE_NODE));

  newNode->source = strdup(source);
  newNode->name = strdup(name);
  newNode->functions = NULL;
  newNode->classes = NULL;
  newNode->locked = false;

  newNode->next = map->head;
  map->head = newNode;

  return true;
}

internal MODULE_NODE*
MAP_getModule(MAP* map, const char* name) {
  MODULE_NODE* node = map->head;
  while (node != NULL) {
    if (STRINGS_EQUAL(node->name, name)) {
      return node;
    } else {
      node = node->next;
    }
  }
  return NULL;
}

internal const char*
MAP_getSource(MAP* map, const char* moduleName) {
  MODULE_NODE* module = MAP_getModule(map, moduleName);
  if (module == NULL) {
    // We don't have the module, but it might be built into Wren (aka Random,Meta)
    return NULL;
  }

  const char* file = strdup(module->source);
  return file;
}

internal bool
MAP_addFunction(MAP* map, const char* moduleName, const char* signature, WrenForeignMethodFn fn) {
  MODULE_NODE* module = MAP_getModule(map, moduleName);
  // TODO: Check for duplicate?
  if (module != NULL && !module->locked) {
    FUNCTION_NODE* node = (FUNCTION_NODE*) malloc(sizeof(FUNCTION_NODE));
    node->signature = strdup(signature);
    node->fn = fn;

    node->next = module->functions;
    module->functions = node;
    return true;
  }
  return false;
}

internal bool
MAP_addClass(MAP* map, const char* moduleName, const char* className, WrenForeignMethodFn allocate, WrenFinalizerFn finalize) {
  MODULE_NODE* module = MAP_getModule(map, moduleName);
  // TODO: Check for duplicate?
  if (module != NULL && !module->locked) {
    CLASS_NODE* node = (CLASS_NODE*) malloc(sizeof(CLASS_NODE));
    node->name = strdup(className);
    node->methods.allocate = allocate;
    node->methods.finalize = finalize;

    node->next = module->classes;
    module->classes = node;
    return true;
  }
  return false;
}

internal void
MAP_lockModule(MAP* map, const char* name) {
  MODULE_NODE* module = MAP_getModule(map, name);
  if (module != NULL) {
    module->locked = true;
  }
}

internal WrenForeignMethodFn
MAP_getFunction(MAP* map, const char* moduleName, const char* signature) {
  MODULE_NODE* module = MAP_getModule(map, moduleName);
  if (module == NULL) {
    // We don't have the module, but it might be built into Wren (aka Random,Meta)
    return NULL;
  }
  assert(module != NULL);

  FUNCTION_NODE* node = module->functions;
  while (node != NULL) {
    if (STRINGS_EQUAL(signature, node->signature)) {
      return node->fn;
    } else {
      node = node->next;
    }
  }
  return NULL;
}

internal bool
MAP_getClassMethods(MAP* map, const char* moduleName, const char* className, WrenForeignClassMethods* methods) {
  MODULE_NODE* module = MAP_getModule(map, moduleName);
  if (module == NULL) {
    // We don't have the module, but it might be built into Wren (aka Random,Meta)
    return false;
  }
  assert(module != NULL);

  CLASS_NODE* node = module->classes;
  while (node != NULL) {
    if (STRINGS_EQUAL(className, node->name)) {
      *methods = node->methods;
      return true;
    } else {
      node = node->next;
    }
  }
  return false;
}


internal void
MAP_freeFunctions(FUNCTION_NODE* node) {
  FUNCTION_NODE* nextNode;
  while (node != NULL) {
    nextNode = node->next;
    free(node->signature);
    free(node);
    node = nextNode;
  }
}
internal void
MAP_freeClasses(CLASS_NODE* node) {
  CLASS_NODE* nextNode;
  while (node != NULL) {
    nextNode = node->next;
    free(node->name);
    free(node);
    node = nextNode;
  }
}

internal void
MAP_free(MAP* map) {
  MODULE_NODE* module = map->head;
  MODULE_NODE* nextModule;
  while (module != NULL) {
    MAP_freeFunctions(module->functions);
    MAP_freeClasses(module->classes);
    nextModule = module->next;
    free(module->name);
    free(module->source);
    free(module);
    module = nextModule;
  }
}

internal void
MAP_init(MAP* map) {
  map->head = NULL;
#include "modulemap.c.inc"
}

<file_sep>/Makefile
# Paths
SOURCE=src
LIBS=lib
OBJS=obj
INCLUDES=include
SOURCE_FILES = $(shell find src -type f)
UTILS = $(SOURCE)/util
MODULES=$(SOURCE)/modules
SCRIPTS=scripts


# Build flags
# Each must have distinct values for dimension

# MODE: release or debug
MODE ?= release

# Determine the system
# ARCH = 64bit or 32bit
UNAME_S = $(shell uname -s)
UNAME_P = $(shell uname -p)
ifeq ($(UNAME_S), Darwin)
SYSTEM ?= macosx
ARCH ?= 64bit
FRAMEWORK ?= $(shell which sdl2-config 1>/dev/null && echo "" || echo "framework")
else ifeq ($(UNAME_S), Linux)
SYSTEM ?= linux
ARCH ?= 64bit
else
SYSTEM ?= windows
ifneq (,$(findstring 32,$(UNAME_S)))
	ARCH ?= 32bit
else
	ARCH ?= 64bit
endif
endif

# 0 or 1
STATIC ?= 0
TAGS = $(ARCH) $(SYSTEM) $(MODE) $(FRAMEWORK)
OBJS := $(OBJS)/$(ARCH)

ifeq ($(STATIC), 1)
TAGS += static
else 
TAGS += shared
endif

ifndef verbose
  SILENT = @
endif

# Compute Variables based on build flags

ifneq ($(and $(filter windows,$(TAGS)),$(filter 64bit,$(TAGS))),)
TARGET_NAME ?= dome-x64
ICON_OBJECT_FILE ?= assets/icon64.res
else ifneq ($(and $(filter windows,$(TAGS)),$(filter 32bit,$(TAGS))),)
TARGET_NAME ?= dome-x32
ICON_OBJECT_FILE ?= assets/icon32.res
else
TARGET_NAME ?= dome
endif

BUILD_VALUE=$(shell git rev-parse --short HEAD)
DOME_OPTS = -DDOME_HASH=\"$(BUILD_VALUE)\"
ifdef DOME_OPT_VERSION
  DOME_OPTS += -DDOME_VERSION=\"$(DOME_OPT_VERSION)\"
else
  DOME_OPTS += -DDOME_VERSION=\"$(shell git describe --tags)\"
endif

SDL_CONFIG ?= $(shell which sdl2-config 1>/dev/null && echo "sdl2-config" || (which "$(LIBS)/sdl2-config" 1>/dev/null && echo "$(LIBS)/sdl2-config" || echo ""))

# Compiler configurations

WARNING_FLAGS = -Wall -Wno-unused-parameter -Wno-unused-function -Wno-unused-value

ifneq ($(filter macosx,$(TAGS)),)
WARNING_FLAGS += -Wno-incompatible-pointer-types-discards-qualifiers
else ifneq ($(filter windows,$(TAGS)),)
WARNING_FLAGS += -Wno-discarded-qualifiers -Wno-clobbered
else ifneq ($(filter linux,$(TAGS)),)
	WARNING_FLAGS += -Wno-clobbered -Wno-maybe-uninitialized -Wno-attributes
endif


CFLAGS = $(DOME_OPTS) -std=c99 -pedantic $(WARNING_FLAGS) -fvisibility=hidden
ifneq ($(filter macosx,$(TAGS)),)
CFLAGS += -mmacosx-version-min=10.12
endif

ifneq ($(filter release,$(TAGS)),)
CFLAGS += -O3
else ifneq ($(filter debug,$(TAGS)),)
CFLAGS += -g -O0 
ifneq ($(filter macosx,$(TAGS)),)
CFLAGS += -fsanitize=address
FFLAGS += -fsanitize=address
endif
endif

# Include Configuration
IFLAGS = -isystem $(INCLUDES)
ifneq (,$(findstring sdl2-config, $(SDL_CONFIG)))
IFLAGS += $(shell $(SDL_CONFIG) --cflags)
endif
ifneq ($(filter static,$(TAGS)),)
IFLAGS := -I$(INCLUDES)/SDL2 $(IFLAGS)
else ifneq ($(filter framework,$(TAGS)),)
IFLAGS += -I/Library/Frameworks/SDL2.framework/Headers
endif


# Compute Link Settings
DEPS = -lm

ifneq (,$(findstring sdl2-config, $(SDL_CONFIG)))
ifneq ($(filter static,$(TAGS)),)
SDLFLAGS=$(shell $(SDL_CONFIG) --static-libs)
else
SDLFLAGS=$(shell $(SDL_CONFIG) --libs)
endif
endif

ifneq ($(filter release,$(TAGS)),)
DEPS += -lwren
else ifneq ($(filter debug,$(TAGS)),)
DEPS += -lwrend
endif
ifneq ($(and $(filter windows,$(TAGS)),$(filter static,$(TAGS))),)
WINDOW_MODE ?= windows
WINDOW_MODE_FLAG = -m$(WINDOW_MODE)
STATIC_FLAG += -static

else ifneq ($(and $(filter macosx,$(TAGS)), $(filter framework,$(TAGS)), $(filter shared,$(TAGS))),)
FFLAGS += -F/Library/Frameworks -framework SDL2
endif

LDFLAGS = -L$(LIBS) $(WINDOW_MODE_FLAG) $(SDLFLAGS) $(STATIC_FLAG) $(DEPS) 




# Build Rules
PROJECTS := dome.bin
.PHONY: all clean reset cloc $(PROJECTS)

all: $(PROJECTS)

WREN_LIB ?= $(LIBS)/libwren.a
WREN_PARAMS ?= $(ARCH) WREN_OPT_RANDOM=0 WREN_OPT_META=1   
$(LIBS)/wren/lib/libwren.a:
	@echo "==== Cloning Wren ===="
	git submodule update --init -- $(LIBS)/wren
$(LIBS)/wren: $(LIBS)/wren/lib/libwren.a
$(WREN_LIB): $(LIBS)/wren
	@echo "==== Building Wren ===="
	./scripts/setup_wren.sh $(WREN_PARAMS)

$(MODULES)/*.inc: $(UTILS)/embed.c $(MODULES)/*.wren
	@echo "==== Building DOME modules  ===="
	./scripts/generateEmbedModules.sh

$(OBJS)/vendor.o: $(INCLUDES)/vendor.c
	@mkdir -p $(OBJS)
	@echo "==== Building vendor module ===="
	$(CC) $(CFLAGS) -c $(INCLUDES)/vendor.c -o $(OBJS)/vendor.o $(IFLAGS)

$(OBJS)/main.o: $(SOURCE_FILES) $(INCLUDES) $(WREN_LIB) $(MODULES)/*.inc
	@mkdir -p $(OBJS)
	@echo "==== Building core ($(TAGS)) module ===="
	$(CC) $(CFLAGS) -c $(SOURCE)/main.c -o $(OBJS)/main.o $(IFLAGS) 

$(TARGET_NAME): $(OBJS)/main.o $(OBJS)/vendor.o $(WREN_LIB)
	@echo "==== Linking DOME ($(TAGS)) ===="
	$(CC) $(CFLAGS) $(FFLAGS) -o $(TARGET_NAME) $(OBJS)/*.o $(ICON_OBJECT_FILE) $(LDFLAGS) 
	./scripts/set-executable-path.sh $(TARGET_NAME)
	@echo "DOME built as $(TARGET_NAME)"

dome.bin: $(TARGET_NAME)

clean: 
	rm -rf $(TARGET_NAME) $(MODULES)/*.inc
	rm -rf $(OBJS)/*.o
reset:
	git submodule foreach --recursive git clean -xfd
	rm -rf $(LIBS)/libwren.a 
	rm -rf $(LIBS)/libwrend.a
	rm -rf $(INCLUDES)/wren.h

cloc:
	cloc --by-file --force-lang="java",wren --fullpath --not-match-d "util" -not-match-f ".inc" src
<file_sep>/docs/plugins/example.md
[< Back](.)

Plugin Example
============
This example has two parts: A plugin file and a DOME application which loads it:

## DOME application

This is a basic example of loading a plugin, and making use of an external module
to access native methods.

```c++
import "plugin" for Plugin

Plugin.load("test")
// The plugin will be initialised now

// Plugins can register their own modules
import "external" for ExternalClass


class Main {
  construct new() {}
  init() {
    // and allocators for foreign classes
    var obj = ExternalClass.init()

    // and finally, they can register foreign methods implemented
    // in the plugin native language.
    obj.alert("Some words")
  }
  update() {}
  draw(dt) {}
}

var Game = Main.new()
```


## Plugin library

A plugin can be written in any language that can compile down to a DLL, .so or .dylib file. This example will use C.

```c
// Include some basic C definitions
#include <stddef.h>

// You'll need to include the DOME header
#include "dome.h"

static DOME_API_v0* core;
static WREN_API_v0* wren;

static const char* source =  ""
"foreign class ExternalClass {\n" // Source file for an external module
  "construct init() {} \n"
  "foreign alert(text) \n"
"} \n";

void allocate(WrenVM* vm) {
  size_t CLASS_SIZE = 0; // This should be the size of your object's data
  void* obj = wren->setSlotNewForeign(vm, 0, 0, CLASS_SIZE);
}

void alertMethod(WrenVM* vm) {
  // Fetch the method argument
  const char* text = wren->getSlotString(vm, 1);

  // Retrieve the DOME Context from the VM. This is needed for many things.
  DOME_Context ctx = core->getContext(vm);

  core->log(ctx, "%s\n", text);
}

DOME_EXPORT DOME_Result PLUGIN_onInit(DOME_getAPIFunction DOME_getAPI,
    DOME_Context ctx) {

  // Fetch the latest Core API and save it for later use.
  core = DOME_getAPI(API_DOME, DOME_API_VERSION);

  // DOME also provides a subset of the Wren API for accessing slots
  // in foreign methods.
  wren = DOME_getAPI(API_WREN, WREN_API_VERSION);

  core->log(ctx, "Initialising external module\n");

  // Register a module with it's associated source.
  // Avoid giving the module a common name.
  core->registerModule(ctx, "external", source);

  core->registerClass(ctx, "external", "ExternalClass", allocate, NULL);
  core->registerFn(ctx, "external", "ExternalClass.alert(_)", alertMethod);

  // Returning anything other than SUCCESS here will result in the current fiber
  // aborting. Use this to indicate if your plugin initialised successfully.
  return DOME_RESULT_SUCCESS;
}

DOME_EXPORT DOME_Result PLUGIN_preUpdate(DOME_Context ctx) {
  return DOME_RESULT_SUCCESS;
}

DOME_EXPORT DOME_Result PLUGIN_postUpdate(DOME_Context ctx) {
  return DOME_RESULT_SUCCESS;
}
DOME_EXPORT DOME_Result PLUGIN_preDraw(DOME_Context ctx) {
  return DOME_RESULT_SUCCESS;
}
DOME_EXPORT DOME_Result PLUGIN_postDraw(DOME_Context ctx) {
  return DOME_RESULT_SUCCESS;
}

DOME_EXPORT DOME_Result PLUGIN_onShutdown(DOME_Context ctx) {
  return DOME_RESULT_SUCCESS;
}
```

## Compiling your plugin

Compiling plugins is a little tricky, as each platform has slightly different requirements.
This section attempts to demonstrate ways of doing this on the common platforms.
Place the resulting compiled library file in the directory with your `main.wren` or `game.egg` and it should be detected by DOME.

The `dome.h` file which provides the developer API is located in `../../include`, so we make sure this is added to the include folder list.
You can adjust this to suit your development environment as necessary.

### Mac OS X
```
> gcc -dynamiclib -o pluginName.dylib -I../../include plugin.c -undefined dynamic_lookup
```

We tell it we are building a `dynamiclib`, and that we want to treat `undefined` functions as fine using `dynamic_lookup`, so that methods can be acquired and linked at runtime.

### Windows
```
> gcc -O3 -std=gnu11 -shared -fPIC  -I../../include pluginName.c -Wl,--unresolved-symbols=ignore-in-object-files -o pluginName.dll
```
This example uses MSYS/MinGW for compiling the library on Windows, but there's no reason you couldn't use Visual Studio to compile a plugin instead.

### Linux:
```
> gcc -O3 -std=c11 -shared -o pluginName.so -fPIC  -I../../include pluginName.c
```

<file_sep>/examples/plugin/Makefile
.PHONY: test.dylib
test.dylib: test.c
	gcc -dynamiclib -o test.dylib -I../../include test.c -undefined dynamic_lookup

test.so: test.c
	gcc -O3 -std=c11 -shared -o test.so -fPIC  -I../../include test.c

test.dll: test.c
	gcc -O3 -std=gnu11 -shared -fPIC  -I../../include test.c -Wl,--unresolved-symbols=ignore-in-object-files -o test.dll
<file_sep>/docs/modules/io.md
[< Back](.)

io
================

The `io` module provides an interface to the host machine's file system, in order to read and write data files.

It contains the following classes:

* [FileSystem](#filesystem)

## FileSystem

### Static Methods

#### `static basePath(): String`
This returns the path to the directory where your application's entry point is.

#### `static listDirectories(path: String): List<String>`
Returns a list of all directories contained in the directory

#### `static listFiles(path: String): List<String>`
Returns a list of all files in the directory.

#### `static load(path: String): String`
Given a valid file `path`, this loads the file data into a String object.
This is a blocking operation, and so execution will stop while the file is loaded.

#### `static prefPath(org: String, appName: String): String`
This gives you a safe path where you can write personal game files (saves and settings), which are specific to this application. The given `org` and `appName` should be unique to this application. Either or both values may end up in the given file path, so they must adhere to some specific rules. Use letters, numbers, spaces, underscores. Avoid other punctuation.

#### `static save(path: String, buffer: String): Void`
Given a valid file `path`, this will create or overwrite the file the data in the `buffer` String object.
This is a blocking operation, and so execution will stop while the file is saved.

#### `static createDirectory(path: String): Void`
Given a valid `path`, creates the directory, if it doesn't already exist and makes parent directories as needed.
<file_sep>/src/modules/audio.c
internal float* resample(float* data, size_t srcLength, uint64_t srcFrequency, uint64_t targetFrequency, size_t* destLength);

internal void
AUDIO_allocate(WrenVM* vm) {
  wrenEnsureSlots(vm, 1);
  AUDIO_DATA* data = (AUDIO_DATA*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(AUDIO_DATA));
  int length;
  ASSERT_SLOT_TYPE(vm, 1, STRING, "buffer");
  const char* fileBuffer = wrenGetSlotBytes(vm, 1, &length);

  int16_t* tempBuffer;

  if (strncmp(fileBuffer, "RIFF", 4) == 0 &&
      strncmp(&fileBuffer[8], "WAVE", 4) == 0) {
    data->audioType = AUDIO_TYPE_WAV;

    // Loading the WAV file
    SDL_RWops* src = SDL_RWFromConstMem(fileBuffer, length);
    void* result = SDL_LoadWAV_RW(src, 1, &data->spec, ((uint8_t**)&tempBuffer), &data->length);
    if (result == NULL) {
      VM_ABORT(vm, "Invalid WAVE file");
      return;
    }
    data->length /= sizeof(int16_t) * data->spec.channels;
  } else if (strncmp(fileBuffer, "OggS", 4) == 0) {
    data->audioType = AUDIO_TYPE_OGG;

    int channelsInFile = 0;
    int freq = 0;
    memset(&data->spec, 0, sizeof(SDL_AudioSpec));
    // Loading the OGG file
    int32_t result = stb_vorbis_decode_memory((const unsigned char*)fileBuffer, length, &channelsInFile, &freq, &tempBuffer);
    if (result == -1) {
      VM_ABORT(vm, "Invalid OGG file");
      return;
    }
    data->length = result;

    data->spec.channels = channelsInFile;
    data->spec.freq = freq;
    data->spec.format = AUDIO_F32LSB;
  } else {
    VM_ABORT(vm, "Audio file was of an incompatible format");
    return;
  }

  data->buffer = calloc(data->length, bytesPerSample);
  assert(data->buffer != NULL);
  assert(data->length != UINT32_MAX);
  // Process incoming values into a mixable format
  for (uint32_t i = 0; i < data->length; i++) {
    data->buffer[i * channels] = (float)(tempBuffer[i * data->spec.channels]) / INT16_MAX;
    if (data->spec.channels == 1) {
      data->buffer[i * channels + 1] = (float)(tempBuffer[i * data->spec.channels]) / INT16_MAX;
    } else {
      data->buffer[i * channels + 1] = (float)(tempBuffer[i * data->spec.channels + 1]) / INT16_MAX;
    }
  }
  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* audioEngine = engine->audioEngine;
  if (data->spec.freq != audioEngine->spec.freq) {
    size_t newLength = 0;
    void* oldPtr = data->buffer;
    data->buffer = resample(data->buffer, data->length, data->spec.freq, audioEngine->spec.freq, &newLength);
    data->length = newLength;
    free(oldPtr);
  }
  // free the intermediate buffers
  if (data->audioType == AUDIO_TYPE_WAV) {
    SDL_FreeWAV((uint8_t*)tempBuffer);
  } else if (data->audioType == AUDIO_TYPE_OGG) {
    free(tempBuffer);
  }
  if (DEBUG_MODE) {
    DEBUG_printAudioSpec(engine, data->spec, data->audioType);
  }
}


internal void
AUDIO_finalize(void* data) {
  AUDIO_DATA* audioData = (AUDIO_DATA*)data;
  if (audioData->buffer != NULL) {
    if (audioData->audioType == AUDIO_TYPE_WAV || audioData->audioType == AUDIO_TYPE_OGG) {
      free(audioData->buffer);
    }
    audioData->buffer = NULL;
  }
}
internal void
AUDIO_unload(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, FOREIGN, "audio data");
  AUDIO_DATA* data = (AUDIO_DATA*)wrenGetSlotForeign(vm, 0);
  AUDIO_finalize(data);
}

internal void
AUDIO_getLength(WrenVM* vm) {
  AUDIO_DATA* data = (AUDIO_DATA*)wrenGetSlotForeign(vm, 0);
  wrenEnsureSlots(vm, 1);
  wrenSetSlotDouble(vm, 0, data->length);
}

internal void
AUDIO_CHANNEL_stop(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;

  CHANNEL* base;
  if (!AUDIO_ENGINE_get(data, ref, &base)) {
    return;
  }
  CHANNEL_requestStop(base);
}

internal void
AUDIO_ENGINE_wrenStopAll(WrenVM* vm) {
  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* audioEngine = engine->audioEngine;
  AUDIO_ENGINE_stopAll(audioEngine);
}

internal void
AUDIO_ENGINE_releaseHandles(AUDIO_ENGINE* audioEngine, WrenVM* vm) {
  TABLE_ITERATOR iter;
  TABLE_iterInit(&iter);
  CHANNEL* channel;
  while (TABLE_iterate(&(audioEngine->playing), &iter)) {
    channel = iter.value;
    channel->state = CHANNEL_STOPPED;
    if (channel->finish != NULL) {
      channel->finish(channel->ref, vm);
    }
  }
  TABLE_iterInit(&iter);
  while (TABLE_iterate(&(audioEngine->pending), &iter)) {
    channel = iter.value;
    channel->state = CHANNEL_STOPPED;
    if (channel->finish != NULL) {
      channel->finish(channel->ref, vm);
    }
  }
}


internal void
AUDIO_CHANNEL_allocate(WrenVM* vm) {
  wrenEnsureSlots(vm, 2);
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(CHANNEL_REF));
  ASSERT_SLOT_TYPE(vm, 1, STRING, "sound id");

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;

  const char* soundId = wrenGetSlotString(vm, 1);
  *ref = AUDIO_CHANNEL_new(data, soundId);
}

internal void
AUDIO_CHANNEL_setAudio(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;

  CHANNEL* base;
  if (!AUDIO_ENGINE_get(data, ref, &base)) {
    return;
  }
  AUDIO_CHANNEL* channel = CHANNEL_getData(base);
  if (CHANNEL_getState(base) < CHANNEL_PLAYING) {
    ASSERT_SLOT_TYPE(vm, 1, FOREIGN, "audio");
    channel->audio = (AUDIO_DATA*)wrenGetSlotForeign(vm, 1);
    channel->audioHandle = wrenGetSlotHandle(vm, 1);
  } else {
    VM_ABORT(vm, "Cannot change audio in channel once initialized");
  }
}

internal void
AUDIO_CHANNEL_setState(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);
  ASSERT_SLOT_TYPE(vm, 1, NUM, "state");

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;

  CHANNEL* base;
  if (!AUDIO_ENGINE_get(data, ref, &base)) {
    return;
  }
  int state = wrenGetSlotDouble(vm, 1);
  if (state <= CHANNEL_INVALID || state >= CHANNEL_LAST) {
    VM_ABORT(vm, "Setting invalid channel state");
  }
  CHANNEL_setState(base, state);
}

internal void
AUDIO_CHANNEL_getState(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;

  CHANNEL* base;
  if (!AUDIO_ENGINE_get(data, ref, &base)) {
    return;
  }
  wrenEnsureSlots(vm, 1);
  wrenSetSlotDouble(vm, 0, CHANNEL_getState(base));
}

internal void
AUDIO_CHANNEL_getId(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);

  wrenEnsureSlots(vm, 1);
  wrenSetSlotDouble(vm, 0, ref->id);
}

internal void
AUDIO_CHANNEL_getSoundId(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;

  CHANNEL* base;
  if (!AUDIO_ENGINE_get(data, ref, &base)) {
    return;
  }
  AUDIO_CHANNEL* channel = CHANNEL_getData(base);
  wrenEnsureSlots(vm, 1);
  wrenSetSlotString(vm, 0, channel->soundId);
}

internal void
AUDIO_CHANNEL_getLength(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;

  CHANNEL* base;
  if (!AUDIO_ENGINE_get(data, ref, &base)) {
    return;
  }
  AUDIO_CHANNEL* channel = CHANNEL_getData(base);
  wrenEnsureSlots(vm, 1);
  wrenSetSlotDouble(vm, 0, channel->audio->length);
}

internal void
AUDIO_CHANNEL_getPosition(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;
  wrenEnsureSlots(vm, 1);
  wrenSetSlotDouble(vm, 0, AUDIO_ENGINE_getPosition(data, ref));
}

internal void
AUDIO_CHANNEL_setLoop(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;

  ASSERT_SLOT_TYPE(vm, 1, BOOL, "loop");
  AUDIO_ENGINE_setLoop(data, ref, wrenGetSlotBool(vm, 1));
}

internal void
AUDIO_CHANNEL_getLoop(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;
  wrenEnsureSlots(vm, 1);
  wrenSetSlotBool(vm, 0, AUDIO_ENGINE_getLoop(data, ref));
}

internal void
AUDIO_CHANNEL_setPosition(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;
  ASSERT_SLOT_TYPE(vm, 1, NUM, "position");
  AUDIO_ENGINE_setPosition(data, ref, round(wrenGetSlotDouble(vm, 1)));
}

internal void
AUDIO_CHANNEL_setVolume(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;
  ASSERT_SLOT_TYPE(vm, 1, NUM, "volume");
  AUDIO_ENGINE_setVolume(data, ref, wrenGetSlotDouble(vm, 1));
}

internal void
AUDIO_CHANNEL_getVolume(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;
  wrenSetSlotDouble(vm, 0, AUDIO_ENGINE_getVolume(data, ref));
}


internal void
AUDIO_CHANNEL_setPan(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;

  ASSERT_SLOT_TYPE(vm, 1, NUM, "pan");
  AUDIO_ENGINE_setPan(data, ref, wrenGetSlotDouble(vm, 1));
}

internal void
AUDIO_CHANNEL_getPan(WrenVM* vm) {
  CHANNEL_REF* ref = (CHANNEL_REF*)wrenGetSlotForeign(vm, 0);

  ENGINE* engine = wrenGetUserData(vm);
  AUDIO_ENGINE* data = engine->audioEngine;
  wrenEnsureSlots(vm, 1);
  wrenSetSlotDouble(vm, 0, AUDIO_ENGINE_getPan(data, ref));
}

internal void
AUDIO_CHANNEL_finalize(void* data) {}

internal double
dbToVolume(double dB) {
  return pow(10.0, 0.05 * dB);
}

internal double
volumeToDb(double volume) {
  return 20.0 * log10(volume);
}

<file_sep>/src/modules/io.c
typedef struct {
  bool ready;
  size_t length;
  char* data;
} DBUFFER;

typedef struct {
  bool complete;
  bool error;
  WrenVM* vm;
  WrenHandle* bufferHandle;
} ASYNCOP;


internal void
ASYNCOP_allocate(WrenVM* vm) {
  // Get a handle to the Stat class. We'll hang on to this so we don't have to
  // look it up by name every time.
  wrenEnsureSlots(vm, 2);
  wrenSetSlotHandle(vm, 1, bufferClass);
  DBUFFER* buffer = (DBUFFER*)wrenSetSlotNewForeign(vm, 1, 1, sizeof(DBUFFER));
  buffer->data = NULL;
  buffer->length = 0;
  buffer->ready = false;

  ASYNCOP* op = (ASYNCOP*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(ASYNCOP));
  op->vm = vm;

  // This is a handle to our specific buffer, not the DataBuffer class
  op->bufferHandle = wrenGetSlotHandle(vm, 1);
  op->complete = false;
  op->error = false;
}

internal void
ASYNCOP_finalize(void* data) {
  ASYNCOP* op = data;
  wrenReleaseHandle(op->vm, op->bufferHandle);
}

internal void
ASYNCOP_getComplete(WrenVM* vm) {
  ASYNCOP* op = (ASYNCOP*)wrenGetSlotForeign(vm, 0);
  wrenEnsureSlots(vm, 1);
  wrenSetSlotBool(vm, 0, op->complete);
}

internal void
ASYNCOP_getResult(WrenVM* vm) {
  ASYNCOP* op = (ASYNCOP*)wrenGetSlotForeign(vm, 0);
  wrenEnsureSlots(vm, 1);
  wrenSetSlotHandle(vm, 0, op->bufferHandle);
}

internal void
DBUFFER_capture(WrenVM* vm) {
  if (bufferClass == NULL) {
    wrenGetVariable(vm, "io", "DataBuffer", 0);
    bufferClass = wrenGetSlotHandle(vm, 0);
  }
}

internal void
DBUFFER_allocate(WrenVM* vm) {
  wrenEnsureSlots(vm, 1);
  DBUFFER* buffer = (DBUFFER*)wrenSetSlotNewForeign(vm, 0, 0, sizeof(DBUFFER));
  buffer->data = NULL;
  buffer->length = 0;
  buffer->ready = false;
}

internal void
DBUFFER_finalize(void* data) {
  DBUFFER* buffer = (DBUFFER*) data;
  if (buffer->ready && buffer->data != NULL) {
    free(buffer->data);
  }
}

internal void
DBUFFER_getLength(WrenVM* vm) {
  DBUFFER* buffer = wrenGetSlotForeign(vm, 0);
  wrenEnsureSlots(vm, 1);
  wrenSetSlotDouble(vm, 0, buffer->length);
}

internal void
DBUFFER_getReady(WrenVM* vm) {
  DBUFFER* buffer = wrenGetSlotForeign(vm, 0);
  wrenEnsureSlots(vm, 1);
  wrenSetSlotBool(vm, 0, buffer->ready);
}

internal void
DBUFFER_getData(WrenVM* vm) {
  DBUFFER* buffer = wrenGetSlotForeign(vm, 0);
  wrenEnsureSlots(vm, 1);
  wrenSetSlotBytes(vm, 0, buffer->data, buffer->length);
}

typedef struct {
  WrenVM* vm;
  WrenHandle* opHandle;
  WrenHandle* bufferHandle;
  char name[256];
  size_t length;
  char* buffer;
} TASK_DATA;

internal void
FILESYSTEM_loadAsync(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, STRING, "file path");
  // Thread: main
  INIT_TO_ZERO(ABC_TASK, task);
  TASK_DATA* taskData = malloc(sizeof(TASK_DATA));

  const char* path = wrenGetSlotString(vm, 1);
  strncpy(taskData->name, path, 255);
  taskData->name[255] = '\0';

  // TODO We probably need to hold a handle to the op
  taskData->vm = vm;
  taskData->opHandle = wrenGetSlotHandle(vm, 2);
  ASYNCOP* op = wrenGetSlotForeign(vm, 2);
  taskData->bufferHandle = op->bufferHandle;
  taskData->buffer = NULL;
  taskData->length = 0;

  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  task.type = TASK_LOAD_FILE;
  task.data = taskData;
  ABC_FIFO_pushTask(&engine->fifo, task);
}

internal void
FILESYSTEM_loadEventHandler(void* data) {
  TASK_DATA* task = data;

  // Thread: Async
  ENGINE* engine = (ENGINE*)wrenGetUserData(task->vm);
  task->buffer = ENGINE_readFile(engine, task->name, &task->length);

  SDL_Event event;
  SDL_memset(&event, 0, sizeof(event));
  event.type = ENGINE_EVENT_TYPE;
  event.user.code = EVENT_LOAD_FILE;
  event.user.data1 = task;
  // TODO: Use this to handle errors in future?
  event.user.data2 = NULL;
  SDL_PushEvent(&event);
}

internal void
FILESYSTEM_saveSync(WrenVM* vm) {
  int length;
  ASSERT_SLOT_TYPE(vm, 1, STRING, "file path");
  ASSERT_SLOT_TYPE(vm, 2, STRING, "file data");
  const char* path = wrenGetSlotString(vm, 1);
  const char* data = wrenGetSlotBytes(vm, 2, &length);
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  ENGINE_WRITE_RESULT result = ENGINE_writeFile(engine, path, data, length);
  if (result == ENGINE_WRITE_PATH_INVALID) {
    size_t len = 22 + strlen(path);
    char message[len];
    snprintf(message, len, "Could not find file: %s", path);
    VM_ABORT(vm, message);
    return;
  }
}

internal void
FILESYSTEM_loadSync(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, STRING, "file path");
  const char* path = wrenGetSlotString(vm, 1);
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);

  size_t length;
  char* data = ENGINE_readFile(engine, path, &length);
  if (data == NULL) {
    size_t len = 22 + strlen(path);
    char message[len];
    snprintf(message, len, "Could not find file: %s", path);
    VM_ABORT(vm, message);
    return;
  }
  wrenEnsureSlots(vm, 1);
  wrenSetSlotBytes(vm, 0, data, length);
  free(data);
}

internal void
FILESYSTEM_loadEventComplete(SDL_Event* event) {
  // Thread: Main
  TASK_DATA* task = event->user.data1;
  WrenVM* vm = task->vm;
  wrenEnsureSlots(vm, 4);

  wrenSetSlotHandle(vm, 1, task->opHandle);
  ASYNCOP* op = (ASYNCOP*)wrenGetSlotForeign(vm, 1);
  wrenSetSlotHandle(vm, 2, task->bufferHandle);
  DBUFFER* buffer = (DBUFFER*)wrenGetSlotForeign(vm, 2);

  buffer->data = task->buffer;
  buffer->length = task->length;
  buffer->ready = true;

  op->complete = true;

  // Free resources and handles
  wrenReleaseHandle(vm, task->opHandle);
  free(task);
}

internal void
FILESYSTEM_listFiles(WrenVM* vm) {
  const char* path = wrenGetSlotString(vm, 1);
  bool shouldFree;
  const char* fullPath = resolvePath(path, &shouldFree);
  tinydir_dir dir;
  int result = tinydir_open(&dir, fullPath);
  if (shouldFree) {
    free((void*)fullPath);
  }
  if (result == -1) {
    VM_ABORT(vm, "Directory could not be opened");
  } else {
    wrenEnsureSlots(vm, 2);
    wrenSetSlotNewList(vm, 0);
    while (dir.has_next) {
      tinydir_file file;
      tinydir_readfile(&dir, &file);
      if (!file.is_dir)
      {
        // Only files
        wrenSetSlotString(vm, 1, file.name);
        // Append slot 1 to the list in slot 0
        wrenInsertInList(vm, 0, -1, 1);
      }
      tinydir_next(&dir);
    }
  }

  tinydir_close(&dir);
}

internal void
FILESYSTEM_listDirectories(WrenVM* vm) {
  const char* path = wrenGetSlotString(vm, 1);
  bool shouldFree;
  const char* fullPath = resolvePath(path, &shouldFree);
  tinydir_dir dir;
  int result = tinydir_open(&dir, fullPath);
  if (shouldFree) {
    free((void*)fullPath);
  }
  if (result == -1) {
    VM_ABORT(vm, "Directory could not be opened");
  } else {
    wrenEnsureSlots(vm, 2);
    wrenSetSlotNewList(vm, 0);
    while (dir.has_next) {
      tinydir_file file;
      tinydir_readfile(&dir, &file);
      if (file.is_dir)
      {
        // Only directories
        wrenSetSlotString(vm, 1, file.name);
        // Append slot 1 to the list in slot 0
        wrenInsertInList(vm, 0, -1, 1);
      }
      tinydir_next(&dir);
    }
  }

  tinydir_close(&dir);
}

internal void
FILESYSTEM_getPrefPath(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, STRING, "organisation");
  ASSERT_SLOT_TYPE(vm, 2, STRING, "application name");
  const char* org = wrenGetSlotString(vm, 1);
  const char* app = wrenGetSlotString(vm, 2);
  char* path = SDL_GetPrefPath(org, app);
  if (path != NULL) {
    wrenSetSlotString(vm, 0, path);
    SDL_free(path);
  } else {
    wrenSetSlotString(vm, 0, BASEPATH_get());
  }
}

internal void
FILESYSTEM_getBasePath(WrenVM* vm) {
  wrenSetSlotString(vm, 0, BASEPATH_get());
}

internal void
FILESYSTEM_createDirectory(WrenVM *vm) {
  const char* path = wrenGetSlotString(vm, 1);
  mode_t mode = 0777;

  bool shouldFree;
  const char* fullPath = resolvePath(path, &shouldFree);
  int result = mkdirp(fullPath, mode);
  if (shouldFree) {
    free((void*)fullPath);
  }
  if (result == -1) {
    VM_ABORT(vm, "Directory could not be created");
  }
}
<file_sep>/docs/modules/input.md
[< Back](.)

input
================

The `input` module allows you to retrieve the state of input devices such as the keyboard, mouse and game controllers.

It contains the following classes:

* [DigitalInput](#digitalinput)
* [Keyboard](#keyboard)
* [Mouse](#mouse)
* [GamePad](#gamepad)

## DigitalInput

An instance of `DigitalInput` represents an input such as a keyboard key, mouse button or controller button, which can be either "pressed" or "unpressed".

### Instance Methods
#### `repeat(): Void`
Causes the current state of the input to be repeated, which can affect the `justPressed` property. This is useful in certain scenarios where an input's state may be tested repeatedly, but before the user has had a chance to release the input.

#### `reset(): Void`
Resets the input state, as if the input was just set to no longer be down. This is useful in certain scenarios where an input's state may be tested repeatedly, but before the user has had a chance to release the input.

### Instance Fields

#### `static down: Boolean`
This is true if the digital input is "pressed" or otherwise engaged.

#### `static justPressed: Boolean`
Returns true if the input was "pressed" down on this tick.

#### `static previous: Boolean`
This gives you the value of "down" on the previous tick, since input was last processed. (Depending on game loop lag, input may be processed once for multiple update ticks.)

#### `static repeats: Number`
This counts the number of ticks that an input has been engaged for. If the input isn't engaged, this should be zero.

## Keyboard

### Static Fields

#### `static allPressed: Map<string, DigitalInput>`
This returns a map containing the key names and corresponding `DigitalInput` objects, for all keys which are currently "down".

### Static Methods

#### `static [name]: DigitalInput`
This returns a digital input of a valid name. See `Keyboard.isKeyDown` for a list of valid names.

#### `static isButtonPressed(key: String): Boolean`
#### `static isKeyDown(key: String): Boolean`
Returns true if the named key is pressed. The key uses the SDL key name, which can be referenced [here](https://wiki.libsdl.org/SDL_Keycode).

## Mouse

### Static Fields

#### `static allPressed: Map<string, DigitalInput>`
This returns a map containing the key names and corresponding `DigitalInput` objects, for all keys which are currently "down".

#### `static hidden: Boolean`
Controls whether the mouse cursor is shown or hidden. You can set and read from this field.

#### `static pos: Vector`
#### `static position: Vector`
Returns a vector of _(Mouse.x, Mouse.y)_ for convenience.

#### `static relative: Boolean`
If set to true, the mouse is placed into relative mode. In this mode, the mouse will be fixed to the center of the screen. You can set and read from this field. This changes the behaviour of the `x` and `y` fields.

#### `static scroll: Vector`
Returns a vector of _(scrollX, scrollY)_, for convenience.

#### `static scrollX: Number`
The total distance the mouse wheel has scrolled horizontally in the past frame. Left is negative and right is positive.

#### `static scrollY: Number`
The total distance the mouse wheel has scrolled vertically in the past frame. Left is negative and down is positive.

#### `static x: Number`
The x position relative to the Canvas. This accounts for the window being resized and the viewport moving. If `Mouse.relative` is set, this will be the relative change of the mouse x position since the previous tick.

#### `static y: Number`
The y position relative to the Canvas. This accounts for the window being resized and the viewport moving. If `Mouse.relative` is set, this will be the relative change of the mouse y position since the previous tick.

### Static Methods

#### `static [name]: DigitalInput`
This returns a digital input of a valid name. See `Mouse.isButtonPressed` for a list of valid names.

#### `static isButtonPressed(name: String/Number): Boolean`
Returns true if the named mouse button is pressed. 
You can use an index from 1-5 (button 0 is invalid) or a lowercase name:
* `left`
* `middle`
* `right`
* `x1`
* `x2`

## GamePad

You can use a game pad as input for your games. DOME expects a game pad similar to those used by popular games consoles, with a D-Pad, face buttons, triggers and analog sticks.

### Static Fields

#### `all: List<GamePad>`
Returns a list of GamePad objects representing all attached gamepads.

#### `next: GamePad`
Returns a GamePad representing an arbitrary attached gamepad. This isn't guarenteed to return the same object every time, so you should cache it.

### Static Methods

#### `[id]: GamePad`
This will return an object representing a GamePad. You can then read the state of that gamepad using the instance methods below.

If no gamepads are attached, you will receive a "dummy" object which will report null or empty values.

### Instance Fields

#### `allPressed: Map<string, DigitalInput>`
This returns a map containing the key names and corresponding `DigitalInput` objects, for all keys which are currently "down".

#### `attached: Boolean`
This returns true if the gamepad is still attached to the system.
#### `id: Number`
Returns the instance id for this gamepad, which can be used to fetch it using `GamePad[id]`.
#### `name: String`
If the gamepad is attached, this returns the SDL internal name for that device.

### Instance Methods

#### `[name]: DigitalInput`
This returns a digital input of a valid name. See `GamePad.isButtonPressed` for a list of valid names.

#### `isButtonPressed(button: String): Boolean`
Returns true if the named button is pressed. Valid button names are:
 * `left` - D-Pad Left
 * `right` - D-Pad Right
 * `up` - D-Pad Up
 * `down` - D-Pad Down
 * `A` - A Button
 * `B` - B Button
 * `X` - X Button
 * `Y` - Y Button
 * `back` - Back Button (Often referred to as "Select")
 * `start` - Start Button
 * `guide` - Guide Button (On an XBox controller, this is the XBox button)
 * `leftstick` - Clicking the left stick
 * `rightstick` - Clicking the right stick
 * `leftshoulder` - Left shoulder button
 * `rightshoulder` - Right shoulder button

These button names are case insensitive.

#### `getTrigger(side: String): Number`
Gets the current state of the trigger on the specified `side`, as a number between 0.0 and 1.0.
Valid sides are `left` and `right`.

#### `getAnalogStick(side: String): Vector`
Gets the current state of the specified analog stick as a Vector, with `x` and `y` values, which are normalised as values between -1.0 and 1.0.
Valid sides are `left` and `right`.

#### `rumble(strength: Number, duration: Number): Void`
If the gamepad is able to vibrate, it will vibrate with a `strength`, clamped between `0.0` and `1.0`, for a `duration` of milliseconds. Rumble and haptic feedback is dependant on platform drivers.
<file_sep>/docs/faq.md
[< Back](.)

Frequently Asked Questions
=================

## Why Wren?

Wren is a great language for making games in:
 * Wren has a simple syntax, and a small core API, so it's easy to learn and work with.
 * Dynamic typing, which makes writing code easy, for fast prototyping.
 * The language runtime is designed to be fast, which is perfect for games.
 * Wren supports functional and object-oriented programming paradigms, making it possible to express your ideas clearly.
 * Finally, it's fun!

## What makes DOME comfortable?
DOME has been designed to provide simple APIs for making games, so that you feel comfortable, whatever you're making.
It aims to rely on as few dependencies as possible, easy to build on most platforms and games made with DOME should be easy to share!

## Have any games been made with DOME?

Sort of! Take a look:
* There's an [example game](https://github.com/domeengine/dome/tree/main/examples/spaceshooter) in the GitHub repository, which shows off a number of features. It's based on [this tutorial](https://ztiromoritz.github.io/pico-8-shooter/) for the PICO-8.
* Check out [this repository](https://github.com/domeengine/dome-examples) for example games taken from "Code the Classics", but ported from Python and pygame to Wren and DOME.
* A entry to "A Game By It's Cover 2018": [Grid](https://avivbeeri.itch.io/grid) - [Source](https://github.com/avivbeeri/grid)
* [Station Salvage](https://avivbeeri.itch.io/station-salvage), a fine 7DRL 2020 entry.
* [CyberRunner](https://github.com/Sandvich/CyberRunner), an experimental endless runner (WIP)
* A entry to "A Game By It's Cover 2020": [ALLNIGHTER](https://avivbeeri.itch.io/allnighter) - Parts of ALLNIGHTER's development were live streamed, with recordings available [here](https://www.youtube.com/channel/UCAk93TqgFFQLabjRsxp6bSA).

<file_sep>/src/engine.h
// Forward-declaring some methods for interacting with the AudioEngine
// for managing memory and initialization
struct AUDIO_ENGINE_t;
internal struct AUDIO_ENGINE_t* AUDIO_ENGINE_init(void);
internal void AUDIO_ENGINE_free(struct AUDIO_ENGINE_t*);

typedef struct {
  int64_t x;
  int64_t y;
  int64_t w;
  int64_t h;
} DOME_RECT;

typedef struct {
  bool relative;
  int x;
  int y;
  int scrollX;
  int scrollY;
} ENGINE_MOUSE_STATE;

typedef struct {
  double avgFps;
  double alpha;
  int32_t elapsed;
  FILE* logFile;
  size_t errorBufLen;
  size_t errorBufMax;
  char* errorBuf;
} ENGINE_DEBUG;

typedef struct {
  bool makeGif;
  uint32_t* gifPixels;
  volatile bool frameReady;
  char* gifName;
} ENGINE_RECORDER;

typedef struct {
  size_t height;
  size_t width;
  uint32_t* pixels;
} PIXEL_BUFFER;

typedef struct {
  void* pixels;
  uint32_t width;
  uint32_t height;
  int32_t offsetX;
  int32_t offsetY;
  DOME_RECT clip;
} CANVAS;

typedef struct ENGINE_t {
  ENGINE_RECORDER record;
  SDL_Window* window;
  SDL_Renderer *renderer;
  SDL_Texture *texture;
  SDL_Rect viewport;
  CANVAS canvas;
  PIXEL_BUFFER blitBuffer;
  ABC_FIFO fifo;
  MAP moduleMap;
  // PLUGIN_MAP pluginMap;
  PLUGIN_COLLECTION plugins;
  mtar_t* tar;
  bool running;
  char** argv;
  size_t argc;
  bool lockstep;
  ENGINE_MOUSE_STATE mouse;
  int exit_status;
  struct AUDIO_ENGINE_t* audioEngine;
  bool initialized;
  bool debugEnabled;
  bool vsyncEnabled;
  ENGINE_DEBUG debug;
} ENGINE;


typedef enum {
  EVENT_NOP,
  EVENT_LOAD_FILE,
  EVENT_WRITE_FILE,
  EVENT_WRITE_FILE_APPEND
} EVENT_TYPE;

typedef enum {
  TASK_NOP,
  TASK_PRINT,
  TASK_LOAD_FILE,
  TASK_WRITE_FILE,
  TASK_WRITE_FILE_APPEND
} TASK_TYPE;

typedef enum {
  ENGINE_WRITE_SUCCESS,
  ENGINE_WRITE_PATH_INVALID
} ENGINE_WRITE_RESULT;

global_variable uint32_t ENGINE_EVENT_TYPE;

<file_sep>/src/vm.c
internal WrenForeignClassMethods
VM_bind_foreign_class(WrenVM* vm, const char* moduleName, const char* className) {
  WrenForeignClassMethods methods;
  // Assume an unknown class.
  methods.allocate = NULL;
  methods.finalize = NULL;

  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  MAP moduleMap = engine->moduleMap;
  MAP_getClassMethods(&moduleMap, moduleName, className, &methods);

  return methods;
}

internal WrenForeignMethodFn VM_bind_foreign_method(
    WrenVM* vm,
    const char* module,
    const char* className,
    bool isStatic,
    const char* signature) {

  // This file is seperate because it has a Copyright notice with it.
#include "signature.c.inc"

  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  MAP moduleMap = engine->moduleMap;
  return MAP_getFunction(&moduleMap, module, fullName);
}

internal WrenLoadModuleResult
VM_load_module(WrenVM* vm, const char* name) {
  WrenLoadModuleResult result = { 0 };

  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  MAP moduleMap = engine->moduleMap;

  if (DEBUG_MODE) {
    ENGINE_printLog(engine, "Loading module %s from ", name);
  }

  // Check against wren optional modules
#if WREN_OPT_META
  if (strcmp(name, "meta") == 0) {
    if (DEBUG_MODE) {
      ENGINE_printLog(engine, "wren\n", name);
    }
    return result;
  }
#endif
#if WREN_OPT_RANDOM
  if (strcmp(name, "random") == 0) {
    if (DEBUG_MODE) {
      ENGINE_printLog(engine, "wren\n", name);
    }
    return result;
  }
#endif

  // Check against dome modules
  char* module = (char*)MAP_getSource(&moduleMap, name);

  if (module != NULL) {
    if (DEBUG_MODE) {
      ENGINE_printLog(engine, "dome\n", name);
    }
    result.source = module;
    return result;
  }

  // Otherwise, search on filesystem
  char* extension = ".wren";
  char* path;
  path = malloc(strlen(name)+strlen(extension)+1); /* make space for the new string (should check the return value ...) */
  strcpy(path, name); /* add the extension */
  strcat(path, extension); /* add the extension */

  if (DEBUG_MODE) {
    ENGINE_printLog(engine, "%s\n", engine->tar ? "egg bundle" : "filesystem");
  }

  // This pointer becomes owned by the WrenVM and freed later.
  char* file = ENGINE_readFile(engine, path, NULL);
  free(path);

  result.source = file;
  return result;
}

// Debug output for VM
internal void VM_write(WrenVM* vm, const char* text) {
  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  ENGINE_printLog(engine, "%s", text);
}

internal void VM_error(WrenVM* vm, WrenErrorType type, const char* module,
    int line, const char* message) {

  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);

  if (DEBUG_MODE == false) {
    MAP moduleMap = engine->moduleMap;

    if (module != NULL && MAP_getModule(&moduleMap, module) != NULL) {
      return;
    }
  }


  size_t bufSize = 255;
  char error[bufSize];

  if (type == WREN_ERROR_COMPILE) {
    snprintf(error, bufSize, "%s:%d: %s\n", module, line, message);
  } else if (type == WREN_ERROR_RUNTIME) {
    snprintf(error, bufSize, "Runtime error: %s\n", message);
  } else if (type == WREN_ERROR_STACK_TRACE) {
    snprintf(error, bufSize, "  %d: %s\n", line, module);
  }
  size_t len = strlen(error);
  while ((len + engine->debug.errorBufLen) >= engine->debug.errorBufMax) {
    char* oldBuf = engine->debug.errorBuf;
    engine->debug.errorBufMax += 64;
    engine->debug.errorBuf = realloc(engine->debug.errorBuf, sizeof(char) * engine->debug.errorBufMax);
    if (engine->debug.errorBufMax == 64) {
      engine->debug.errorBuf[0] = '\0';
    }
    if (engine->debug.errorBuf == NULL) {
      // If we can't allocate more memory, rollback to the old pointer.
      engine->debug.errorBuf = oldBuf;
      engine->debug.errorBufMax -= 64;
      return;
    }
  }
  strcat(engine->debug.errorBuf, error);
  engine->debug.errorBufLen += len;
}

internal const char*
VM_resolve_module_name(WrenVM* vm, const char* importer, const char* name) {
  const char* localName = name;
  if (strlen(name) > 1) {
    while (localName[0] == '.' && localName[1] == '/') {
      localName = localName + 2;
    }
  }
  return path_normalize(localName);
}

internal WrenVM* VM_create(ENGINE* engine) {
  WrenConfiguration config;
  wrenInitConfiguration(&config);
  config.writeFn = VM_write;
  config.errorFn = VM_error;
  config.bindForeignMethodFn = VM_bind_foreign_method;
  config.bindForeignClassFn = VM_bind_foreign_class;
  config.resolveModuleFn = VM_resolve_module_name;
  config.loadModuleFn = VM_load_module;

  WrenVM* vm = wrenNewVM(&config);
  wrenSetUserData(vm, engine);

  // Set modules

  // StringUtils
  MAP_addFunction(&engine->moduleMap, "stringUtils", "static StringUtils.toLowercase(_)", STRING_UTILS_toLowercase);
  MAP_lockModule(&engine->moduleMap, "stringUtils");

  // DOME
  MAP_addFunction(&engine->moduleMap, "dome", "static Process.f_exit(_)", PROCESS_exit);
  MAP_addFunction(&engine->moduleMap, "dome", "static Process.args", PROCESS_getArguments);
  MAP_addFunction(&engine->moduleMap, "dome", "static Window.resize(_,_)", WINDOW_resize);
  MAP_addFunction(&engine->moduleMap, "dome", "static Window.title=(_)", WINDOW_setTitle);
  MAP_addFunction(&engine->moduleMap, "dome", "static Window.vsync=(_)", WINDOW_setVsync);
  MAP_addFunction(&engine->moduleMap, "dome", "static Window.lockstep=(_)", WINDOW_setLockStep);
  MAP_addFunction(&engine->moduleMap, "dome", "static Window.title", WINDOW_getTitle);
  MAP_addFunction(&engine->moduleMap, "dome", "static Window.fullscreen=(_)", WINDOW_setFullscreen);
  MAP_addFunction(&engine->moduleMap, "dome", "static Window.fullscreen", WINDOW_getFullscreen);
  MAP_addFunction(&engine->moduleMap, "dome", "static Window.width", WINDOW_getWidth);
  MAP_addFunction(&engine->moduleMap, "dome", "static Window.height", WINDOW_getHeight);
  MAP_addFunction(&engine->moduleMap, "dome", "static Window.fps", WINDOW_getFps);
  MAP_addFunction(&engine->moduleMap, "dome", "static Window.f_color=(_)", WINDOW_setColor);
  MAP_addFunction(&engine->moduleMap, "dome", "static Window.f_color", WINDOW_getColor);
  MAP_addFunction(&engine->moduleMap, "dome", "static Version.toString", VERSION_getString);
  MAP_lockModule(&engine->moduleMap, "dome");

  // Canvas
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_pset(_,_,_)", CANVAS_pset);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_pget(_,_)", CANVAS_pget);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_rectfill(_,_,_,_,_)", CANVAS_rectfill);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_cls(_)", CANVAS_cls);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_rect(_,_,_,_,_)", CANVAS_rect);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_line(_,_,_,_,_,_)", CANVAS_line);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_circle(_,_,_,_)", CANVAS_circle);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_circlefill(_,_,_,_)", CANVAS_circle_filled);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_ellipse(_,_,_,_,_)", CANVAS_ellipse);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_ellipsefill(_,_,_,_,_)", CANVAS_ellipsefill);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_print(_,_,_,_)", CANVAS_print);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.offset(_,_)", CANVAS_offset);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.clip(_,_,_,_)", CANVAS_clip);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.f_resize(_,_,_)", CANVAS_resize);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.width", CANVAS_getWidth);
  MAP_addFunction(&engine->moduleMap, "graphics", "static Canvas.height", CANVAS_getHeight);
  MAP_lockModule(&engine->moduleMap, "graphics");

  // Font
  MAP_addClass(&engine->moduleMap, "font", "FontFile", FONT_allocate, FONT_finalize);
  MAP_addClass(&engine->moduleMap, "font", "RasterizedFont", FONT_RASTER_allocate, FONT_RASTER_finalize);
  MAP_addFunction(&engine->moduleMap, "font", "RasterizedFont.f_print(_,_,_,_)", FONT_RASTER_print);
  MAP_addFunction(&engine->moduleMap, "font", "RasterizedFont.antialias=(_)", FONT_RASTER_setAntiAlias);
  MAP_lockModule(&engine->moduleMap, "font");

  // Image
  MAP_addClass(&engine->moduleMap, "image", "ImageData", IMAGE_allocate, IMAGE_finalize);
  MAP_addFunction(&engine->moduleMap, "image", "ImageData.f_loadFromFile(_)", IMAGE_loadFromFile);
  MAP_addFunction(&engine->moduleMap, "image", "ImageData.f_getPNG()", IMAGE_getPNG);
  MAP_addFunction(&engine->moduleMap, "image", "ImageData.draw(_,_)", IMAGE_draw);
  MAP_addFunction(&engine->moduleMap, "image", "ImageData.width", IMAGE_getWidth);
  MAP_addFunction(&engine->moduleMap, "image", "ImageData.height", IMAGE_getHeight);
  MAP_addFunction(&engine->moduleMap, "image", "ImageData.f_pget(_,_)", IMAGE_pget);
  MAP_addFunction(&engine->moduleMap, "image", "ImageData.f_pset(_,_,_)", IMAGE_pset);
  MAP_addClass(&engine->moduleMap, "image", "DrawCommand", DRAW_COMMAND_allocate, DRAW_COMMAND_finalize);
  MAP_addFunction(&engine->moduleMap, "image", "DrawCommand.draw(_,_)", DRAW_COMMAND_draw);
  MAP_lockModule(&engine->moduleMap, "image");

  // Audio
  MAP_addClass(&engine->moduleMap, "audio", "SystemChannel", AUDIO_CHANNEL_allocate, AUDIO_CHANNEL_finalize);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.loop=(_)", AUDIO_CHANNEL_setLoop);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.loop", AUDIO_CHANNEL_getLoop);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.pan=(_)", AUDIO_CHANNEL_setPan);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.pan", AUDIO_CHANNEL_getPan);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.volume", AUDIO_CHANNEL_getVolume);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.volume=(_)", AUDIO_CHANNEL_setVolume);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.position=(_)", AUDIO_CHANNEL_setPosition);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.state=(_)", AUDIO_CHANNEL_setState);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.audio=(_)", AUDIO_CHANNEL_setAudio);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.stop()", AUDIO_CHANNEL_stop);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.state", AUDIO_CHANNEL_getState);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.length", AUDIO_CHANNEL_getLength);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.position", AUDIO_CHANNEL_getPosition);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.soundId", AUDIO_CHANNEL_getSoundId);
  MAP_addFunction(&engine->moduleMap, "audio", "SystemChannel.id", AUDIO_CHANNEL_getId);

  // AudioData
  MAP_addClass(&engine->moduleMap, "audio", "AudioData", AUDIO_allocate, AUDIO_finalize);
  MAP_addFunction(&engine->moduleMap, "audio", "AudioData.length", AUDIO_getLength);
  MAP_addFunction(&engine->moduleMap, "audio", "static AudioEngine.f_stopAllChannels()", AUDIO_ENGINE_wrenStopAll);

  MAP_lockModule(&engine->moduleMap, "audio");

  // FileSystem
  MAP_addFunction(&engine->moduleMap, "io", "static FileSystem.f_load(_,_)", FILESYSTEM_loadAsync);
  MAP_addFunction(&engine->moduleMap, "io", "static FileSystem.load(_)", FILESYSTEM_loadSync);
  MAP_addFunction(&engine->moduleMap, "io", "static FileSystem.save(_,_)", FILESYSTEM_saveSync);
  MAP_addFunction(&engine->moduleMap, "io", "static FileSystem.listFiles(_)", FILESYSTEM_listFiles);
  MAP_addFunction(&engine->moduleMap, "io", "static FileSystem.listDirectories(_)", FILESYSTEM_listDirectories);
  MAP_addFunction(&engine->moduleMap, "io", "static FileSystem.prefPath(_,_)", FILESYSTEM_getPrefPath);
  MAP_addFunction(&engine->moduleMap, "io", "static FileSystem.basePath()", FILESYSTEM_getBasePath);
  MAP_addFunction(&engine->moduleMap, "io", "static FileSystem.createDirectory(_)", FILESYSTEM_createDirectory);

  // Buffer
  MAP_addClass(&engine->moduleMap, "io", "DataBuffer", DBUFFER_allocate, DBUFFER_finalize);
  MAP_addFunction(&engine->moduleMap, "io", "static DataBuffer.f_capture()", DBUFFER_capture);
  MAP_addFunction(&engine->moduleMap, "io", "DataBuffer.f_data", DBUFFER_getData);
  MAP_addFunction(&engine->moduleMap, "io", "DataBuffer.ready", DBUFFER_getReady);
  MAP_addFunction(&engine->moduleMap, "io", "DataBuffer.f_length", DBUFFER_getLength);

  // AsyncOperation
  MAP_addClass(&engine->moduleMap, "io", "AsyncOperation", ASYNCOP_allocate, ASYNCOP_finalize);
  MAP_addFunction(&engine->moduleMap, "io", "AsyncOperation.result", ASYNCOP_getResult);
  MAP_addFunction(&engine->moduleMap, "io", "AsyncOperation.complete", ASYNCOP_getComplete);
  MAP_lockModule(&engine->moduleMap, "io");

  // Input
  MAP_addClass(&engine->moduleMap, "input", "SystemGamePad", GAMEPAD_allocate, GAMEPAD_finalize);
  MAP_addFunction(&engine->moduleMap, "input", "static Input.f_captureVariables()", INPUT_capture);
  MAP_addFunction(&engine->moduleMap, "input", "static Mouse.x", MOUSE_getX);
  MAP_addFunction(&engine->moduleMap, "input", "static Mouse.y", MOUSE_getY);
  MAP_addFunction(&engine->moduleMap, "input", "static Mouse.scrollX", MOUSE_getScrollX);
  MAP_addFunction(&engine->moduleMap, "input", "static Mouse.scrollY", MOUSE_getScrollY);
  MAP_addFunction(&engine->moduleMap, "input", "static Mouse.hidden=(_)", MOUSE_setHidden);
  MAP_addFunction(&engine->moduleMap, "input", "static Mouse.hidden", MOUSE_getHidden);
  MAP_addFunction(&engine->moduleMap, "input", "static Mouse.relative=(_)", MOUSE_setRelative);
  MAP_addFunction(&engine->moduleMap, "input", "static Mouse.relative", MOUSE_getRelative);
  MAP_addFunction(&engine->moduleMap, "input", "static SystemGamePad.f_getGamePadIds()", GAMEPAD_getGamePadIds);
  MAP_addFunction(&engine->moduleMap, "input", "SystemGamePad.getTrigger(_)", GAMEPAD_getTrigger);
  MAP_addFunction(&engine->moduleMap, "input", "SystemGamePad.close()", GAMEPAD_close);
  MAP_addFunction(&engine->moduleMap, "input", "SystemGamePad.f_getAnalogStick(_)", GAMEPAD_getAnalogStick);
  MAP_addFunction(&engine->moduleMap, "input", "SystemGamePad.rumble(_,_)", GAMEPAD_rumble);
  MAP_addFunction(&engine->moduleMap, "input", "SystemGamePad.attached", GAMEPAD_isAttached);
  MAP_addFunction(&engine->moduleMap, "input", "SystemGamePad.name", GAMEPAD_getName);
  MAP_addFunction(&engine->moduleMap, "input", "SystemGamePad.id", GAMEPAD_getId);
  MAP_lockModule(&engine->moduleMap, "input");

  // Json
  MAP_addFunction(&engine->moduleMap, "json", "JsonStream.stream_begin(_)", JSON_STREAM_begin);
  MAP_addFunction(&engine->moduleMap, "json", "JsonStream.stream_end()", JSON_STREAM_end);
  MAP_addFunction(&engine->moduleMap, "json", "JsonStream.next", JSON_STREAM_next);
  MAP_addFunction(&engine->moduleMap, "json", "JsonStream.value", JSON_STREAM_value);
  MAP_addFunction(&engine->moduleMap, "json", "JsonStream.error_message", JSON_STREAM_error_message);
  MAP_addFunction(&engine->moduleMap, "json", "JsonStream.lineno", JSON_STREAM_lineno);
  MAP_addFunction(&engine->moduleMap, "json", "JsonStream.pos", JSON_STREAM_pos);
  MAP_addFunction(&engine->moduleMap, "json", "static JsonStream.escapechar(_,_)", JSON_STREAM_escapechar);
  MAP_lockModule(&engine->moduleMap, "json");

  // Platform
  MAP_addFunction(&engine->moduleMap, "platform", "static Platform.time", PLATFORM_getTime);
  MAP_addFunction(&engine->moduleMap, "platform", "static Platform.name", PLATFORM_getName);
  MAP_lockModule(&engine->moduleMap, "platform");

  // Plugin
  MAP_addFunction(&engine->moduleMap, "plugin", "static Plugin.f_load(_)", PLUGIN_load);
  MAP_lockModule(&engine->moduleMap, "plugin");

  // Random
  MAP_addClass(&engine->moduleMap, "random", "Squirrel3", RANDOM_allocate, RANDOM_finalize);
  MAP_addFunction(&engine->moduleMap, "random", "static Squirrel3.noise(_,_)", RANDOM_noise);
  MAP_addFunction(&engine->moduleMap, "random", "Squirrel3.float()", RANDOM_float);
  MAP_lockModule(&engine->moduleMap, "random");

  return vm;
}

internal void VM_free(WrenVM* vm) {
  if (vm != NULL) {
    wrenFreeVM(vm);
  }
}
<file_sep>/docs/modules/async.md
[< Back](.)

io
================

The `io` module provides an interface to the host machine's file system, in order to read and write data files.

It contains the following classes:

* [FileSystem](#filesystem)
* [DataBuffer](#databuffer)
* [AsyncOperation](#asyncoperation)

## FileSystem

### Static Methods

#### `static load(path)`


## DataBuffer

### Instance fields

#### `data`
#### `length`
#### `ready`


## AsyncOperation

### Instance fields

#### `complete`
#### `result`
<file_sep>/docs/modules/json.md
[< Back](.)

json
================

The `json` module provides a simple interface to read and write [json files and strings](https://www.json.org/json-en.html).

It contains the following classes:

* [Json](#json)
* [JsonOptions](#jsonoptions)
* [JsonError](#jsonerror)

## Json

### Static Methods

#### `static encode(object: Object): String`

#### `static encode(object: Object, options: Num): String`

Transform the object to a _Json_ encoded string. With default or custom options.
Encoding only works for primitive data types (_Bool_, _Map_, _Num_, _Null_, _List_, _String_). If a non primitive object is passed, the encoder will call it's _toString_ method.

#### `static decode(value: String, options: Num): Object`

#### `static decode(value: String): Object`

Returns a new _Json_ object with default or custom options.

#### `static load(path: String): Object`

#### `static load(path: String, options: Num): Object`

Reads the contents of a file in `path` and returns a new _Json_ object with default or custom options.

#### `static save(path: String, object: Object)`

#### `static save(path: String, object: Object, options: Num)`

This will encode the object and then save the result to a file specified in `path`. With default or custom options.

### Examples

#### Encoding a Simple Object

A simple object made with only primitive data structures.

```js
Json.encode({
 "is": true
})
```

#### Encoding a Complex Object

An object made with custom data structures.

```js
import "json" for Json

class MyClass {
  // override toString to provide a serializable representation
  // of the object. This serialization will be called by the
  // Json.encode() method.
  toString { Json.encode(toMap) }
  toMap { { "is": isTrue } }

  isTrue { _isTrue }

  construct new(isTrue) {
    _isTrue = isTrue
  }
}

var obj = MyClass.new(true)

// prints: { "is":true }
System.print(Json.encode(obj))
```

## JsonOptions

### Constants

#### `static nil: Num`

No options selected.

#### `static escapeSlashes: Num`

This will encode solidus character (`/`). When converting a `Map` object to a `String`. This encoding is optional and is useful when you need to embed `JSON` inside `HTML <script>` tags. By default _DOME_ does not escape slashes.

#### `static abortOnError: Num`

By default _DOME_ aborts when there is a _JSON parsing error_ (triggers a `Fiber.abort()` on parse error). Turn off this option if you want to capture the _JsonError_ object.

#### `static checkCircular: Num`

By default _DOME_ checks, when encoding, if your JSON contains circles, and aborts if it does. Turn off this option to not perform this check. This can speed up serializing, but will trigger an infinite recursion if the JSON indeed contains circles.

### Example

Use [Bitwise OR](https://wren.io/method-calls.html#operators) operator to select multiple options.

```js
Json.decode(myString, JsonOptions.escapeSlashes | JsonOptions.abortOnError);
```

## JsonError

This object is returned on calls to _Json.decode_ only if the `JsonOptions.abortOnError` default behaviour is disabled and a parse error was found.

### Instance Properties

#### `line: Num`

Stores the last parsed line number.

#### `position: Num`

Stores the last parsed cursor position.

#### `message: String`

Stores the generated error message.

#### `found: Bool`

Tells if an error was found.
<file_sep>/src/modules/image.c
typedef struct {
  int32_t width;
  int32_t height;
  uint32_t* pixels;
  int32_t channels;
} IMAGE;

typedef enum { COLOR_MODE_RGBA, COLOR_MODE_MONO } COLOR_MODE;

typedef struct {
  IMAGE* image;
  VEC scale;
  double angle;
  double opacity;

  int32_t srcW;
  int32_t srcH;

  iVEC src;
  VEC dest;

  COLOR_MODE mode;
  // MONO color palette
  uint32_t backgroundColor;
  uint32_t foregroundColor;
  // Tint color value
  uint32_t tintColor;
} DRAW_COMMAND;

internal DRAW_COMMAND
DRAW_COMMAND_init(IMAGE* image) {
  DRAW_COMMAND command;
  command.image = image;

  command.src = (iVEC) { 0, 0 };
  command.srcW = image->width;
  command.srcH = image->height;
  command.dest = (VEC) { 0, 0 };

  command.angle = 0;
  command.scale = (VEC) { 1, 1 };

  command.opacity = 1;

  command.mode = COLOR_MODE_RGBA;
  command.backgroundColor = 0xFF000000;
  command.foregroundColor = 0xFFFFFFFF;
  command.tintColor = 0x00000000;

  return command;
}

internal void
DRAW_COMMAND_execute(ENGINE* engine, DRAW_COMMAND* commandPtr) {

  DRAW_COMMAND command = *commandPtr;
  IMAGE* image = command.image;

  iVEC src = command.src;
  int32_t srcW = command.srcW;
  int32_t srcH = command.srcH;

  VEC dest = command.dest;

  double angle = command.angle;
  VEC scale = command.scale;

  uint32_t* pixel = (uint32_t*)image->pixels;
  pixel = pixel + (src.y * image->width + src.x);


  int direction = round(angle / 90);
  direction %= 4;
  if(direction < 0) direction += 4;

  double sX = (1.0 / scale.x);
  double sY = (1.0 / scale.y);

  if (direction >= 0) {
    double swap;
    sX = fabs(sX);
    sY = fabs(sY);

    int32_t w = (srcW) * fabs(scale.x);
    int32_t h = (srcH) * fabs(scale.y);

    if (direction & 1) {
      swap = w;
      w = h;
      h = swap;
    }
    for (int32_t j = 0; j < h; j++) {
      for (int32_t i = 0; i < w; i++) {

        int32_t x = dest.x + i;
        int32_t y = dest.y + j;

        double q = i * sX;
        double t = j * sY;

        int32_t u = (q);
        int32_t v = (t);

        if ((scale.y > 0 && direction >= 2) || (scale.y < 0 && direction < 2)) {
          y = dest.y + (h-1) - j;
        }

        bool flipX = ((direction == 1 || direction == 2));
        if ((scale.x < 0 && !flipX) || (scale.x > 0 && flipX)) {
          x = dest.x + (w-1) - i;
        }

        if (direction & 1) {
          swap = u;
          u = v;
          v = swap;
        }

        // Prevent out-of-bounds access
        if ((src.x + u) < 0 || (src.x + u) >= image->width || (src.y + v) < 0 || (src.y + v) >= image->height) {
          continue;
        }

        uint32_t preColor = *(pixel + (v * image->width + u));
        uint32_t color = preColor;
        uint8_t alpha = (0xFF000000 & color) >> 24;
        if (command.mode == COLOR_MODE_MONO) {
          if (alpha < 0xFF || (color & 0x00FFFFFF) == 0) {
            color = command.backgroundColor;
          } else {
            color = command.foregroundColor;
          }
        } else {
          color = ((uint8_t)(alpha * command.opacity) << 24) | (preColor & 0x00FFFFFF);
        }
        ENGINE_pset(engine, x, y, color);
        // Only apply the tint on visible pixels
        if (command.tintColor != 0 && color != 0 && (alpha * command.opacity) != 0) {
          ENGINE_pset(engine, x, y, command.tintColor);
        }
      }
    }
  }
}


internal void
DRAW_COMMAND_allocate(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, FOREIGN, "image");
  ASSERT_SLOT_TYPE(vm, 2, LIST, "parameters");

  DRAW_COMMAND* command = (DRAW_COMMAND*)wrenSetSlotNewForeign(vm,
      0, 0, sizeof(DRAW_COMMAND));

  IMAGE* image = wrenGetSlotForeign(vm, 1);
  *command = DRAW_COMMAND_init(image);

  wrenGetListElement(vm, 2, 0, 1);
  ASSERT_SLOT_TYPE(vm, 1, NUM, "angle");
  command->angle = wrenGetSlotDouble(vm, 1);

  wrenGetListElement(vm, 2, 1, 1);
  ASSERT_SLOT_TYPE(vm, 1, NUM, "scale.x");
  command->scale.x = wrenGetSlotDouble(vm, 1);

  wrenGetListElement(vm, 2, 2, 1);
  ASSERT_SLOT_TYPE(vm, 1, NUM, "scale.y");
  command->scale.y = wrenGetSlotDouble(vm, 1);

  if (wrenGetListCount(vm, 2) > 3) {
    wrenGetListElement(vm, 2, 3, 1);
    ASSERT_SLOT_TYPE(vm, 1, NUM, "source X");
    command->src.x = wrenGetSlotDouble(vm, 1);

    wrenGetListElement(vm, 2, 4, 1);
    ASSERT_SLOT_TYPE(vm, 1, NUM, "source Y");
    command->src.y = wrenGetSlotDouble(vm, 1);

    wrenGetListElement(vm, 2, 5, 1);
    ASSERT_SLOT_TYPE(vm, 1, NUM, "source width");
    command->srcW = wrenGetSlotDouble(vm, 1);

    wrenGetListElement(vm, 2, 6, 1);
    ASSERT_SLOT_TYPE(vm, 1, NUM, "source height");
    command->srcH = wrenGetSlotDouble(vm, 1);

    wrenGetListElement(vm, 2, 7, 1);
    ASSERT_SLOT_TYPE(vm, 1, STRING, "color mode");
    const char* mode = wrenGetSlotString(vm, 1);
    if (STRINGS_EQUAL(mode, "MONO")) {
      command->mode = COLOR_MODE_MONO;
    } else {
      command->mode = COLOR_MODE_RGBA;
    }

    wrenGetListElement(vm, 2, 8, 1);
    ASSERT_SLOT_TYPE(vm, 1, NUM, "foreground color");
    command->foregroundColor = wrenGetSlotDouble(vm, 1);

    wrenGetListElement(vm, 2, 9, 1);
    ASSERT_SLOT_TYPE(vm, 1, NUM, "background color");
    command->backgroundColor = wrenGetSlotDouble(vm, 1);

    wrenGetListElement(vm, 2, 10, 1);
    ASSERT_SLOT_TYPE(vm, 1, NUM, "opacity");
    command->opacity = wrenGetSlotDouble(vm, 1);

    wrenGetListElement(vm, 2, 11, 1);
    ASSERT_SLOT_TYPE(vm, 1, NUM, "tint color");
    command->tintColor = wrenGetSlotDouble(vm, 1);
  }
}

internal void
DRAW_COMMAND_finalize(void* data) {
  // Nothing here
}

internal void
DRAW_COMMAND_draw(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y");

  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  DRAW_COMMAND* command = wrenGetSlotForeign(vm, 0);

  command->dest.x = wrenGetSlotDouble(vm, 1);
  command->dest.y = wrenGetSlotDouble(vm, 2);

  DRAW_COMMAND_execute(engine, command);
}

internal void
IMAGE_allocate(WrenVM* vm) {
  IMAGE* image = (IMAGE*)wrenSetSlotNewForeign(vm,
      0, 0, sizeof(IMAGE));
  image->pixels = NULL;
  if (wrenGetSlotCount(vm) < 3) {
    image->width = 0;
    image->height = 0;
    image->channels = 0;
  } else {
    ASSERT_SLOT_TYPE(vm, 1, NUM, "width");
    ASSERT_SLOT_TYPE(vm, 2, NUM, "height");
    image->width = wrenGetSlotDouble(vm, 1);
    image->height = wrenGetSlotDouble(vm, 2);
    image->channels = 4;

    image->pixels = calloc(image->width * image->height, image->channels * sizeof(uint8_t));
  }
}

internal void
IMAGE_finalize(void* data) {
  IMAGE* image = data;

  if (image->pixels != NULL) {
    free(image->pixels);
  }
}

internal void
IMAGE_loadFromFile(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 0, FOREIGN, "ImageData");
  ASSERT_SLOT_TYPE(vm, 1, STRING, "image");
  int length;
  const char* fileBuffer = wrenGetSlotBytes(vm, 1, &length);
  IMAGE* image = (IMAGE*)wrenGetSlotForeign(vm, 0);
  image->pixels = (uint32_t*)stbi_load_from_memory((const stbi_uc*)fileBuffer, length,
      &image->width,
      &image->height,
      &image->channels,
      STBI_rgb_alpha);

  if (image->pixels == NULL)
  {
    const char* errorMsg = stbi_failure_reason();
    size_t errorLength = strlen(errorMsg);
    char buf[errorLength + 8];
    snprintf(buf, errorLength + 8, "Error: %s\n", errorMsg);
    wrenSetSlotString(vm, 0, buf);
    wrenAbortFiber(vm, 0);
    return;
  }
}

internal void
IMAGE_draw(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y");

  ENGINE* engine = (ENGINE*)wrenGetUserData(vm);
  IMAGE* image = (IMAGE*)wrenGetSlotForeign(vm, 0);
  int32_t x = wrenGetSlotDouble(vm, 1);
  int32_t y = wrenGetSlotDouble(vm, 2);
  if (image->channels == 2 || image->channels == 4) {
    // drawCommand
    DRAW_COMMAND command = DRAW_COMMAND_init(image);
    command.dest = (VEC){ x, y };
    DRAW_COMMAND_execute(engine, &command);
  } else {
    // fast blit
    size_t height = image->height;
    size_t width = image->width;
    uint32_t* pixels = image->pixels;
    for (size_t j = 0; j < height; j++) {
      uint32_t* row = pixels + (j * width);
      ENGINE_blitLine(engine, x, y + j, width, row);
    }
  }
}

internal void
IMAGE_getWidth(WrenVM* vm) {
  IMAGE* image = (IMAGE*)wrenGetSlotForeign(vm, 0);
  wrenSetSlotDouble(vm, 0, image->width);
}

internal void
IMAGE_getHeight(WrenVM* vm) {
  IMAGE* image = (IMAGE*)wrenGetSlotForeign(vm, 0);
  wrenSetSlotDouble(vm, 0, image->height);
}

internal void
IMAGE_pset(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 0, FOREIGN, "image");
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y");
  ASSERT_SLOT_TYPE(vm, 3, NUM, "color");

  IMAGE* image = (IMAGE*)wrenGetSlotForeign(vm, 0);
  int64_t width = image->width;
  int64_t height = image->height;
  int64_t x = round(wrenGetSlotDouble(vm, 1));
  int64_t y = round(wrenGetSlotDouble(vm, 2));
  uint64_t color = wrenGetSlotDouble(vm, 3);
  if (0 <= x && x < width && 0 <= y && y < height) {
    uint32_t* pixels = image->pixels;
    pixels[y * width + x] = color;
  } else {
    VM_ABORT(vm, "pset co-ordinates out of bounds")
  }
}

internal void
IMAGE_pget(WrenVM* vm) {
  ASSERT_SLOT_TYPE(vm, 0, FOREIGN, "image");
  ASSERT_SLOT_TYPE(vm, 1, NUM, "x");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "y");

  IMAGE* image = (IMAGE*)wrenGetSlotForeign(vm, 0);
  int64_t width = image->width;
  int64_t height = image->height;
  int64_t x = round(wrenGetSlotDouble(vm, 1));
  int64_t y = round(wrenGetSlotDouble(vm, 2));
  if (0 <= x && x < width && 0 <= y && y < height) {
    uint32_t* pixels = image->pixels;
    uint32_t c = pixels[y * width + x];
    wrenSetSlotDouble(vm, 0, c);
  } else {
    VM_ABORT(vm, "pget co-ordinates out of bounds")
  }
}

internal void
IMAGE_getPNGOutput(void* vm, void* data, int size) {
  wrenSetSlotBytes(vm, 0, data, size);
}

internal void
IMAGE_getPNG(WrenVM* vm) {
  IMAGE* image = (IMAGE*)wrenGetSlotForeign(vm, 0);
  ASSERT_SLOT_TYPE(vm, 0, FOREIGN, "image");

  if (image->pixels != NULL) {
    stbi_write_png_to_func(IMAGE_getPNGOutput, vm, image->width, image->height, image->channels, image->pixels, image->width * sizeof(uint8_t) * image->channels);
  } else {
    wrenSetSlotNull(vm, 0);
  }
}

<file_sep>/src/audio/hashmap.c
// Hash table based on the implementation in "Crafting Interpreters"
// as well as the Wren VM.

#define TABLE_MAX_LOAD 0.75
#define NIL_KEY 0

#define IS_TOMBSTONE(entry) ((entry)->value.state == CHANNEL_LAST)
#define IS_EMPTY(entry) ((entry)->value.state == CHANNEL_INVALID)

// <NAME>, Integer Hash Functions.
// http://www.concentric.net/~Ttwang/tech/inthash.htm
internal inline  uint32_t
hashBits(uint64_t hash)
{
  // Wren VM cites v8's computeLongHash() function which in turn cites:
  // <NAME>, Integer Hash Functions.
  // http://www.concentric.net/~Ttwang/tech/inthash.htm
  hash = ~hash + (hash << 18);  // hash = (hash << 18) - hash - 1;
  hash = hash ^ (hash >> 31);
  hash = hash * 21;  // hash = (hash + (hash << 2)) + (hash << 4);
  hash = hash ^ (hash >> 11);
  hash = hash + (hash << 6);
  hash = hash ^ (hash >> 22);
  return (uint32_t)(hash & 0x3fffffff);
}

global_variable const CHANNEL TOMBSTONE  = {
  .state = CHANNEL_LAST,
  .ref = {
    .id = NIL_KEY
  }
};
global_variable const CHANNEL EMPTY_CHANNEL  = {
  .state = CHANNEL_INVALID,
  .ref = {
    .id = NIL_KEY
  }
};

typedef struct {
  uint32_t next;
  uint32_t found;
  bool done;
  CHANNEL* value;
} TABLE_ITERATOR;

void TABLE_iterInit(TABLE_ITERATOR* iter) {
  iter->next = 0;
  iter->found = 0;
  iter->done = false;
  iter->value = NULL;
}

typedef struct {
  CHANNEL_ID key;
  CHANNEL value;
} ENTRY;

typedef struct {
  uint32_t items;
  uint32_t count;
  uint32_t capacity;
  ENTRY* entries;
} TABLE;

void TABLE_init(TABLE* table) {
  table->count = 0;
  table->items = 0;
  table->capacity = 0;
  table->entries = NULL;
}

void TABLE_free(TABLE* table) {
  if (table->entries != NULL) {
    free(table->entries);
  }
  TABLE_init(table);
}

internal bool
TABLE_iterate(TABLE* table, TABLE_ITERATOR* iter) {
  CHANNEL* value = NULL;
  if (iter->found >= table->items) {
    iter->done = true;
  }
  if (!iter->done) {
    for (uint32_t i = iter->next; i < table->capacity; i++) {
      ENTRY* entry = &table->entries[i];
      if (entry->key == NIL_KEY) {
        continue;
      }
      iter->next = i + 1;
      iter->found++;
      value = &entry->value;
      break;
    }
    iter->value = value;
  }
  return !iter->done;
}

internal bool
TABLE_findEntry(ENTRY* entries, uint32_t capacity, CHANNEL_ID key, ENTRY** result) {
  if (capacity == 0) {
    return false;
  }

  uint32_t startIndex = hashBits(key) % capacity;
  uint32_t index = startIndex;
  ENTRY* tombstone = NULL;
  do {
    ENTRY* entry = &(entries[index]);
    if (entry->key == NIL_KEY) {
      if (IS_EMPTY(entry)) {
        *result = tombstone != NULL ? tombstone : entry;
        return false;
      } else {
        if (tombstone == NULL) {
          tombstone = entry;
        }
      }
    } else if (entry->key == key) {
      *result = entry;
      return true;
    }

    index = (index + 1) % capacity;
  } while (index != startIndex);
  *result = tombstone;
  return false;
}

internal void
TABLE_resize(TABLE* table, uint32_t capacity) {
  ENTRY* entries = malloc(sizeof(ENTRY) * capacity);
  for (uint32_t i = 0; i < capacity; i++) {
    entries[i].key = NIL_KEY;
    entries[i].value = EMPTY_CHANNEL;
  }
  table->count = 0;
  for (uint32_t i = 0; i < table->capacity; i++) {
    ENTRY* entry = &table->entries[i];
    if (entry->key == NIL_KEY) {
      continue;
    }
    ENTRY* dest = NULL;
    bool found = TABLE_findEntry(entries, capacity, entry->key, &dest);
    if (!found) {
      assert(dest != NULL);
      dest->key = entry->key;
      dest->value = entry->value;
      table->count++;
    }
  }
  free(table->entries);

  table->capacity = capacity;
  table->entries = entries;
}

internal CHANNEL*
TABLE_set(TABLE* table, CHANNEL_ID key, CHANNEL channel) {
  if ((table->items + 1) > table->capacity * TABLE_MAX_LOAD) {
    uint32_t capacity = table->capacity < 4 ? 4 : table->capacity * 2;
    TABLE_resize(table, capacity);
  }
  ENTRY* entry = NULL;
  bool found = TABLE_findEntry(table->entries, table->capacity, key, &entry);
  if (found) {
    entry->value = channel;
  } else {
    if (!IS_TOMBSTONE(entry)) {
      table->count++;
    }
    table->items++;
    entry->key = key;
    entry->value = channel;
  }

  return &(entry->value);
}

internal bool
TABLE_get(TABLE* table, CHANNEL_ID key, CHANNEL** channel) {
  if (table->count == 0) {
    *channel = NULL;
    return false;
  }
  ENTRY* entry;
  bool found = TABLE_findEntry(table->entries, table->capacity, key, &entry);
  if (found) {
    *channel = &entry->value;
    return true;
  }
  return false;
}

internal bool
TABLE_delete(TABLE* table, CHANNEL_ID key) {
  if (table->count == 0) {
    return false;
  }
  ENTRY* entry;
  bool found = TABLE_findEntry(table->entries, table->capacity, key, &entry);
  if (found) {
    table->items--;
    entry->key = NIL_KEY;
    entry->value = TOMBSTONE;
    return true;
  }
  return false;
}

internal void
TABLE_addAll(TABLE* dest, TABLE* src) {
  for (uint32_t i = 0; i < src->capacity; i++) {
    ENTRY* entry = &src->entries[i];
    if (entry->key != NIL_KEY) {
      TABLE_set(dest, entry->key, entry->value);
    }
  }
}
<file_sep>/src/util/wrenembed.c
// Converts a Wren source file to a C include file
// Using standard IO only
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifndef WRENEMBED_c
#define WRENEMBED_c

char* WRENEMBED_readEntireFile(char* path, size_t* lengthPtr)
{
  FILE* file = fopen(path, "r");
  if (file == NULL) {
    return NULL;
  }
  
  char* source = NULL;
  if (fseek(file, 0L, SEEK_END) == 0) {
    
    // Get the size of the file.
    long bufsize = ftell(file);
    
    // Allocate our buffer to that size.
    source = malloc(sizeof(char) * (bufsize + 1));

    // Go back to the start of the file.
    if (fseek(file, 0L, SEEK_SET) != 0) {
      // Error
    }

    // Read the entire file into memory.
    size_t newLen = fread(source, sizeof(char), bufsize, file);
    
    if (ferror(file) != 0) {
      fclose(file);
      return NULL;
    }
    
    if (lengthPtr != NULL) {
      *lengthPtr = newLen;
    }

    // Add NULL, Just to be safe.
    source[newLen++] = '\0';
  }

  fclose(file);
  return source;
}

int WRENEMBED_encodeAndDump(int argc, char* args[])
{
  if (argc < 2) {
    fputs("Not enough arguments\n", stderr);
    return EXIT_FAILURE;
  }

  size_t length;
  char* fileName = args[1];
  char* fileToConvert = WRENEMBED_readEntireFile(fileName, &length);

  if (fileToConvert == NULL) {
    fputs("Error reading file\n", stderr);
    return EXIT_FAILURE;
  }

  // TODO: Maybe use the filename as a default identifier
  char* moduleName = "wren_module_test";

  if(argc > 2) {
    // TODO: Maybe sanitize moduleName to be valid C identifier?
    moduleName = args[2];
  }
  
  FILE *fp;
  if(argc > 3) {
    fp = fopen(args[3], "w+");
  } else {
    // Example: main.wren.inc
    fp = fopen(strcat(strdup(fileName), ".inc"), "w+");
  }

  fputs("// auto-generated file, do not modify\n", fp);
  fputs("const char ", fp);
  fputs(moduleName, fp);
  fputs("[", fp);
  fprintf(fp, "%li", length + 1);
  fputs("] = {", fp);

  // Encode chars
  for (size_t i = 0; i < length; i++ ) {
    char* ptr = fileToConvert + i;
    if (*ptr == '\n') {
      fputs("'\\n',", fp);
      fputs("\n", fp);
    } else {
      fputs("'", fp);

      // TODO: Properly test the encoding with different source files
      if (*ptr == '\'') {
        fputs("\\\'", fp);
      } else {
        fwrite(ptr, sizeof(char), 1, fp);
      }

      fputs("', ", fp);
    }
  }

  fputs("\0", fp);
  fputs(" };\n", fp);

  fclose(fp);
  free(fileToConvert);

  return EXIT_SUCCESS;
}

void WRENEMBED_usage() {
  fputs("dome -e | --embed sourceFile [moduleName] [destinationFile]\n", stderr);
}

int WRENEMBED_encodeAndDumpInDOME(int argc, char* args[])
{
  // Function to be used inside DOME that adapts argc and args
  // Removing the first arg.
  int count = argc - 1;

  if (count < 2) {
    fputs("Not enough arguments\n", stderr);
    WRENEMBED_usage();
    return EXIT_FAILURE;
  }

  char* argv[count];
  int index;

  for(index = 0; index < count; index++) {
    argv[index] = args[index + 1];
  }

  int exit = WRENEMBED_encodeAndDump(count, argv);
  if (exit == EXIT_FAILURE) {
    WRENEMBED_usage();
  }
  return exit;
}

#endif
<file_sep>/src/modules/json.c
// JSON Parser Events
const char json_typename[][64] = {
    [JSON_ERROR]      = "com.domeengine.json:JSON_ERROR",
    [JSON_DONE]       = "com.domeengine.json:JSON_DONE",
    [JSON_OBJECT]     = "com.domeengine.json:JSON_OBJECT",
    [JSON_OBJECT_END] = "com.domeengine.json:JSON_OBJECT_END",
    [JSON_ARRAY]      = "com.domeengine.json:JSON_ARRAY",
    [JSON_ARRAY_END]  = "com.domeengine.json:JSON_ARRAY_END",
    [JSON_STRING]     = "com.domeengine.json:JSON_STRING",
    [JSON_NUMBER]     = "com.domeengine.json:JSON_NUMBER",
    [JSON_TRUE]       = "com.domeengine.json:JSON_TRUE",
    [JSON_FALSE]      = "com.domeengine.json:JSON_FALSE",
    [JSON_NULL]       = "com.domeengine.json:JSON_NULL",
};

json_stream jsonStream[1];

internal void
JSON_STREAM_begin(WrenVM * vm) {
  ASSERT_SLOT_TYPE(vm, 1, STRING, "value");
  const char * value = wrenGetSlotString(vm, 1);
  json_open_string(jsonStream, value);
  json_set_streaming(jsonStream, 0);
}

internal void
JSON_STREAM_end(WrenVM * vm) {
  json_reset(jsonStream);
  json_close(jsonStream);
}

internal void
JSON_STREAM_value(WrenVM * vm) {
  const char * value = json_get_string(jsonStream, 0);
  wrenSetSlotString(vm, 0, value);
}

internal void
JSON_STREAM_error_message(WrenVM * vm) {
  const char * error = json_get_error(jsonStream);
  if(error) {
    wrenSetSlotString(vm, 0, error);
    return;
  }
  wrenSetSlotString(vm, 0, "");
}

internal void
JSON_STREAM_lineno(WrenVM * vm) {
  wrenSetSlotDouble(vm, 0, json_get_lineno(jsonStream));
}

internal void
JSON_STREAM_pos(WrenVM * vm) {
  wrenSetSlotDouble(vm, 0, json_get_position(jsonStream));
}

internal void
JSON_STREAM_next(WrenVM * vm) {
  enum json_type type = json_next(jsonStream);
  if (json_typename[type]) {
    wrenSetSlotString(vm, 0, json_typename[type]);
    return;
  }
  wrenSetSlotNull(vm, 0);
}

// We have to use C functions for escaping chars
// because a bug in compiler throws error when using \ in strings
enum JSON_DOME_OPTIONS {
    JSON_DOME_NIL_OPTIONS = 0,
    JSON_DOME_ESCAPE_SLASHES = 1,
    JSON_DOME_ABORT_ON_ERROR = 2
};

internal void
JSON_STREAM_escapechar(WrenVM * vm) {
  ASSERT_SLOT_TYPE(vm, 1, STRING, "value");
  ASSERT_SLOT_TYPE(vm, 2, NUM, "options");

  const char * value = wrenGetSlotString(vm, 1);
  int options = (int) wrenGetSlotDouble(vm, 2);

  const char * result = value;

  /*
  "\0" // The NUL byte: 0.
  "\"" // A double quote character.
  "\\" // A backslash.
  "\a" // Alarm beep. (Who uses this?)
  "\b" // Backspace.
  "\f" // Formfeed.
  "\n" // Newline.
  "\r" // Carriage return.
  "\t" // Tab.
  "\v" // Vertical tab.
  */
  if (utf8cmp(value, "\0") == 0) {
    result = "\\0";
  } else if (utf8cmp(value, "\"") == 0) {
    result = "\\\"";
  } else if (utf8cmp(value, "\\") == 0) {
    result = "\\\\";
  } else if (utf8cmp(value, "\\") == 0) {
    result = "\\a";
  } else if (utf8cmp(value, "\b") == 0) {
    result = "\\b";
  } else if (utf8cmp(value, "\f") == 0) {
    result = "\\f";
  } else if (utf8cmp(value, "\n") == 0) {
    result = "\\n";
  } else if (utf8cmp(value, "\r") == 0) {
    result = "\\r";
  } else if (utf8cmp(value, "\t") == 0) {
    result = "\\t";
  } else if (utf8cmp(value, "\v") == 0) {
    result = "\\v";
  } else if (utf8cmp(value, "/") == 0) {
    // Escape / (solidus, slash)
    // https://stackoverflow.com/a/9735430
    // The feature of the slash escape allows JSON to be embedded in HTML (as SGML) and XML.
    // https://www.w3.org/TR/html4/appendix/notes.html#h-B.3.2
    // This is optional escaping. Disabled by default.
    // use JsonOptions.escapeSlashes option to enable it
    if ((options & JSON_DOME_ESCAPE_SLASHES) != JSON_DOME_NIL_OPTIONS) {
        result = "\\/";
    }
  }

  wrenSetSlotString(vm, 0, result);
}
<file_sep>/docs/modules/index.md
[< Back](..)

Modules
============

DOME includes a number of modules to interact with the user through graphics and audio, access their inputs and hard drive. It also has a few useful utilities for mathematical operations.

The modules you can import are here:

* [audio](audio)
* [dome](dome)
* [graphics](graphics)
* [input](input)
* [io](io)
* [json](json)
* [math](math)
* [plugin](plugin)
* [random](random)

For example, the `graphics` module can be imported to access the `Canvas` and `Color` classes, like this:

```wren
import "graphics" for Canvas, Color

...

Canvas.cls()
Canvas.print("Hello world", 20, 20, Color.white)
```
