Parin Tour (WIP)
August 9, 2026 ยท View on GitHub
This guide will go over some of the features of the engine and provide examples of how to use them. If you notice anything missing or want to contribute, feel free to open an issue!
Getting Started
This section shows how to install Parin using DUB. To begin, make a new folder and run inside the following commands to create a new project:
dub init -t parin
If everything is set up correctly, there should be some new files inside the folder. Three of them are particularly important:
source: Contains the source codeassets: Contains the game assetsweb: Contains web related files
Additionally, an app.d file is inside the source folder that looks like this:
import parin;
// Called once when the game starts.
void ready() {
lockResolution(320, 180);
}
// Called every frame while the game is running.
// If true is returned, then the game will stop running.
bool update(float dt) {
drawText("Hello world!", Vec2(8));
return false;
}
// Called once when the game ends.
void finish() {}
// Creates a main function that calls the given functions.
mixin runGame!(ready, update, finish);
This code will create a window that displays the message "Hello world!". Below is a breakdown of how it works.
-
The
readyfunction:void ready() { lockResolution(320, 180); }This function is the starting point of the game. It is called once when the game starts and, in this example, locks the game resolution to 320x180.
-
The
updatefunction:bool update(float dt) { drawText("Hello world!", Vec2(8)); return false; }This function is the main loop of the game. It is called every frame while the game is running and, in this example, draws the message "Hello world!" at position (8, 8). The
return falsestatement at the end indicates that the game should continue running. Iftruewere returned, then the game would stop running. -
The
finishfunction:void finish() {}This function is the ending point of the game. It is called once when the game ends and, in this example, does nothing.
-
The
runGamemixin template:mixin runGame!(ready, update, finish);This mixin sets up a main function that opens a window and calls the ready, update and finish functions. By default, the window has a size of 960x540.
A Parin game typically (can be changed) relies on three functions:
- A function that is called at the start
- A function that is called every frame
- A function that is called at the end
To run the game, use the following command:
dub run
And that's the basics. As a fun exercise, try changing the message to "DVD" and make it bounce inside the window. A solution can be found in the examples folder.
Modules
Parin consists of the following modules:
parin.engine: Engine functionalityparin.types: Common engine typesparin.ui: UI library (WIP)parin.addons: Extras like microuiparin.backend: Backend functionalityparin.bindings: Bindings like Emscriptenparin.joka: Joka library
The parin.engine, parin.types modules are the only mandatory ones for creating a game.
All other modules are optional and can be included as needed.
The import parin statement in the first example is a convenience module that publicly imports parin.engine, parin.types, and parin.ui.
Note
The parin.engine and parin.types modules are the most stable ones. Other modules may change as the engine grows.
Input
Parin provides a set of input functions and types. These include:
/// Returns the current mouse position on the window.
Vec2 mouse();
/// Returns the change in mouse position since the last frame.
Vec2 deltaMouse();
/// Returns the change in mouse wheel position since the last frame.
float deltaWheel();
/// Returns true if the specified character is currently pressed.
bool isDown(char key);
/// Returns true if one of the specified characters is currently pressed.
bool isDown(IStr keys);
/// Returns true if one of the specified keyboard keys is currently pressed.
bool isDown(const(Keyboard) key1, const(Keyboard)[] keys...);
/// Returns true if the specified mouse button is currently pressed.
bool isDown(Mouse key);
/// Returns true if the specified gamepad button is currently pressed.
bool isDown(Gamepad key, int id = 0);
/// Returns true if any of the keyboard keys or the gamepad button in the specified binding is currently pressed.
bool isDown(InputBinding binding);
/// Returns true if the specified character was pressed this frame.
bool isPressed(char key);
/// Returns true if one of the specified characters was pressed this frame.
bool isPressed(IStr keys);
/// Returns true if one of the specified keyboard keys was pressed this frame.
bool isPressed(const(Keyboard) key1, const(Keyboard)[] keys...);
/// Returns true if the specified mouse button was pressed this frame.
bool isPressed(Mouse key);
/// Returns true if the specified gamepad button was pressed this frame.
bool isPressed(Gamepad key, int id = 0);
/// Returns true if any of the keyboard keys or the gamepad button in the specified binding was pressed this frame.
bool isPressed(InputBinding binding);
/// Returns true if the specified character was released this frame.
bool isReleased(char key);
/// Returns true if one of the specified characters was released this frame.
bool isReleased(IStr keys);
/// Returns true if one of the specified keyboard keys was released this frame.
bool isReleased(const(Keyboard) key1, const(Keyboard)[] keys...);
/// Returns true if the specified mouse button was released this frame.
bool isReleased(Mouse key);
/// Returns true if the specified gamepad button was released this frame.
bool isReleased(Gamepad key, int id = 0);
/// Returns true if any of the keyboard keys or the gamepad button in the specified binding was released this frame.
bool isReleased(InputBinding binding);
/// Returns the direction from the WASD and arrow keys that are currently down.
Vec2 wasd();
/// Returns the direction from the WASD and arrow keys that were pressed this frame.
Vec2 wasdPressed();
/// Returns the direction from the WASD and arrow keys that were released this frame.
Vec2 wasdReleased();
/// Returns the next recently pressed keyboard key.
Keyboard dequeuePressedKey();
/// Returns the next recently pressed character.
dchar dequeuePressedRune();
/// Maps one logical action to gamepad and keyboard inputs.
struct InputBinding {
Gamepad button; /// The gamepad button.
Keyboard[4] keys; /// The keyboard keys.
/// Sets the gamepad button and keys to the given values.
this(Gamepad button, Keyboard[] keys...);
}
Below are examples showing how to to move text.
-
Using the mouse:
bool update(float dt) { drawText("Text", mouse); return false; } -
Using the arrow keys:
auto position = Vec2(8); bool update(float dt) { position.x += Keyboard.right.isDown - Keyboard.left.isDown; position.y += Keyboard.down.isDown - Keyboard.up.isDown; drawText("Text", position); return false; } -
Using the WASD keys:
auto position = Vec2(8); bool update(float dt) { position.x += 'd'.isDown - 'a'.isDown; position.y += 's'.isDown - 'w'.isDown; drawText("Text", position); return false; } -
Using the WASD or arrow keys
auto position = Vec2(8); bool update(float dt) { position += wasd; drawText("Text", position); return false; }
Drawing
Parin provides a set of drawing functions and types. These include:
/// Attaches the given camera and makes it active.
void attach(ref Camera camera, Rounding type = Rounding.none);
/// Attaches the given viewport and makes it active.
void attach(ViewportId viewport);
/// Detaches the currently active camera.
void detach(ref Camera camera);
/// Detaches the currently active viewport.
void detach(ViewportId viewport);
/// Begins a clipping region using the given area.
void beginClip(Rect area);
/// Ends the active clipping region.
void endClip();
/// Begins a depth sort. Works only with textures.
void beginDepthSort(DepthSortMode mode = DepthSortMode.topDown);
/// Ends a depth sort. Works only with textures.
void endDepthSort();
/// Draws a rectangle with the specified area and color.
void drawRect(Rect area, Rgba color = white, float thickness = -1.0f);
/// Draws a point at the specified location with the given size and color.
void drawVec2(Vec2 point, Rgba color = white, float thickness = 9.0f);
/// Draws a circle with the specified area and color.
void drawCirc(Circ area, Rgba color = white, float thickness = -1.0f);
/// Draws a line with the specified area, thickness, and color.
void drawLine(Line area, Rgba color = white, float thickness = 9.0f);
/// Draws the surface at the given position with the specified draw options.
void drawSurface(ref Surface surface, Vec2 position, DrawOptions options = DrawOptions());
/// Draws a portion of the specified surface at the given position with the specified draw options.
void drawSurfaceArea(ref Surface surface, Rect area, Vec2 position, DrawOptions options = DrawOptions());
/// Draws the texture at the given position with the specified draw options.
void drawTexture(TextureId texture, Vec2 position, DrawOptions options = DrawOptions());
/// Draws a portion of the specified texture at the given position with the specified draw options.
void drawTextureArea(TextureId texture, Rect area, Vec2 position, DrawOptions options = DrawOptions());
/// Draws a 9-slice from the specified texture area at the given target area.
void drawTextureSlice(TextureId texture, Rect area, Rect target, Margin margin, bool canRepeat, DrawOptions options = DrawOptions());
/// Draws a portion of the specified viewport at the given position with the specified draw options.
void drawViewportArea(ViewportId viewport, Rect area, Vec2 position, DrawOptions options = DrawOptions());
/// Draws the viewport at the given position with the specified draw options.
void drawViewport(ViewportId viewport, Vec2 position, DrawOptions options = DrawOptions());
/// Draws a single character from the specified font at the given position with the specified draw options.
Vec2 drawRune(FontId font, dchar rune, Vec2 position, DrawOptions options = DrawOptions());
/// Draws a single character from the default font at the given position with the specified draw options.
Vec2 drawRune(dchar rune, Vec2 position, DrawOptions options = DrawOptions());
/// Draws the specified text with the given font at the given position using the provided draw options.
Vec2 drawText(FontId font, IStr text, Vec2 position, DrawOptions options = DrawOptions(), TextOptions extra = TextOptions());
/// Draws text with the default font at the given position with the provided draw options.
Vec2 drawText(IStr text, Vec2 position, DrawOptions options = DrawOptions(), TextOptions extra = TextOptions());
/// Append a formatted line to the overlay text buffer.
void dprintfln(A...)(IStr fmtStr, A args);
/// Append a line to the overlay text buffer.
void dprintln(A...)(A args);
/// Returns the contents of the overlay text buffer.
IStr dprintBuffer();
/// Sets the font of the overlay text.
void setDprintFont(FontId value);
/// Sets the position of the overlay text.
void setDprintPosition(Vec2 value);
/// Sets the drawing options for the overlay text.
void setDprintOptions(DrawOptions value);
/// Sets the maximum number of overlay text lines.
void setDprintLineCountLimit(Sz value);
/// Sets the visibility state of the overlay text.
void setDprintVisibility(bool value);
/// Toggles the visibility state of the overlay text.
void toggleDprintVisibility();
/// Clears the overlay text.
void clearDprintBuffer();
/// Draws the overlay text now instead of at the end of the frame.
void drawDprintBuffer();
/// Draws debug engine information at the given position with the provided draw options.
void drawDebugEngineInfo(Vec2 screenPoint, Camera camera = Camera(), DrawOptions options = DrawOptions(), bool isLogging = false);
/// Draws debug tile information at the given position with the provided draw options.
void drawDebugTileInfo(int tileWidth, int tileHeight, Vec2 screenPoint, Camera camera = Camera(), DrawOptions options = DrawOptions(), bool isLogging = false);
/// Draws a tile with a texture.
void drawTile(TextureId texture, Tile tile, DrawOptions options = DrawOptions());
/// Draws a tile map with a texture. The view area controls what is visible.
void drawTileMap(Sz N)(TextureId texture, ref GTileMap!N map, Rect viewArea = Rect(), DrawOptions options = DrawOptions());
/// Draws a tile map with a texture. The camera controls what is visible.
void drawTileMap(Sz N)(TextureId texture, ref GTileMap!N map, Camera camera, DrawOptions options = DrawOptions());
/// Options for configuring drawing parameters.
struct DrawOptions {
/// The origin point of the drawn object. This value can be used to force a specific origin.
Vec2 origin = Vec2(0.0f);
/// The scale of the drawn object.
Vec2 scale = Vec2(1.0f);
/// The rotation of the drawn object, in degrees.
float rotation = 0.0f;
/// The color of the drawn object, in RGBA.
Rgba color = white;
/// A value representing the origin point of the drawn object when origin is zero.
Hook hook = Hook.topLeft;
/// A value representing flipping orientations.
Flip flip = Flip.none;
/// A value that can be used by depth sorting functions.
ubyte layer = 0;
/// Sets the rotation to the given value.
this(float rotation, Hook hook = Hook.topLeft, ubyte layer = 0);
/// Sets the scale to the given value.
this(Vec2 scale, Hook hook = Hook.topLeft, ubyte layer = 0);
/// Sets the color to the given value.
this(Rgba color, Hook hook = Hook.topLeft, ubyte layer = 0);
/// Sets the flip to the given value.
this(Flip flip, Hook hook = Hook.topLeft, ubyte layer = 0);
/// Sets the hook to the given value.
this(Hook hook, ubyte layer = 0);
}
/// Options for configuring extra drawing parameters for text.
struct TextOptions {
/// Controls the visibility ratio of the text when visibilityCount is zero, where 0.0 means fully hidden and 1.0 means fully visible.
float visibilityRatio = 1.0f;
/// The width of the aligned text. It is used as a hint and is not enforced.
int alignmentWidth = 0;
/// Controls the visibility count of the text. This value can be used to force a specific character count.
ushort visibilityCount = 0;
/// A value represeting alignment orientations.
Alignment alignment = Alignment.left;
/// Indicates whether the content of the text flows in a right-to-left direction.
bool isRightToLeft = false;
/// Sets the visibility ratio to the given value.
this(float visibilityRatio);
/// Sets the alignment (and its width) to the given value(s).
this(Alignment alignment, int alignmentWidth = 0);
}
To change the default filtering mode for textures, fonts or viewports, call setDefaultFilter.
Below are some drawing examples.
-
Changing the origin and scale:
bool update(float dt) { auto options = DrawOptions(Hook.center); options.scale = Vec2(4 + sin(elapsedTickTime * 4)); drawText("Text", resolution * Vec2(0.5), options); return false; } -
Changing the origin and visibility ratio
bool update(float dt) { auto options = DrawOptions(Hook.center); auto extra = TextOptions(fmod(elapsedTickTime, 2.0)); drawText("Hello.\nThis is some text.", resolution * Vec2(0.5), options, extra); return false; }
Sound
Parin provides a set of sound functions. These include:
/// Plays the given sound. If the sound is already playing, this has no effect.
void playSound(SoundId sound);
/// Stops playback of the given sound.
void stopSound(SoundId sound);
/// Starts playback of the given sound from the beginning.
void startSound(SoundId sound);
/// Pauses playback of the given sound.
void pauseSound(SoundId sound);
/// Resumes playback of the given sound if it was paused.
void resumeSound(SoundId sound);
/// Toggles whether the sound is playing or stopped.
void toggleSoundIsActive(SoundId sound);
/// Toggles whether the sound is paused or resumed.
void toggleSoundIsPaused(SoundId sound);
/// Returns the current master volume level.
float masterVolume();
/// Sets the master volume level.
void setMasterVolume(float value);
Below is a sound example.
-
Playing a sound:
SoundId sound; bool update(float dt) { if (Keyboard.space.isPressed) playSound(sound); return false; }
Loading & Saving
Parin provides a set of loading and saving functions. These include:
/// Loads a surface file (PNG) with default filter and wrap modes.
Surface loadSurface(IStr path);
/// Loads a surface file (PNG) from memory with default filter and wrap modes.
Surface loadSurface(const(ubyte)[] memory, IStr ext = ".png");
/// Loads a texture file (PNG) with default filter and wrap modes.
TextureId loadTexture(IStr path);
/// Loads a texture file (PNG) from memory with default filter and wrap modes.
TextureId loadTexture(const(ubyte)[] memory, IStr ext = ".png");
/// Loads a font file (TTF) with default filter and wrap modes.
FontId loadFont(IStr path, int size, int runeSpacing = -1, int lineSpacing = -1, IStr32 runes = "");
/// Loads a font file (TTF) from memory with default filter and wrap modes.
FontId loadFont(const(ubyte)[] memory, int size, int runeSpacing = -1, int lineSpacing = -1, IStr32 runes = "", IStr ext = ".ttf");
/// Loads a font file (TTF) from a texture with default filter and wrap modes.
FontId loadFont(TextureId texture, int tileWidth, int tileHeight);
/// Loads a sound file (WAV, OGG, MP3) with default playback settings.
SoundId loadSound(IStr path, float volume, float pitch, bool canRepeat, float pitchVariance = 1.0f);
/// Loads a viewport with default filter and wrap modes.
ViewportId loadViewport(int width, int height, Rgba color, Blend blend = Blend.alpha);
/// Loads bytes from a file and returns the contents as a list.
LStr loadBytes(IStr path);
/// Loads bytes from a file into a temporary buffer for the current frame.
IStr loadTempBytes(IStr path, Sz capacity = defaultEngineLoadOrSaveTextCapacity);
/// Loads bytes from a file into the given buffer.
Fault loadBytesIntoBuffer(L = LStr)(IStr path, ref L listBuffer);
/// Saves bytes into a file with the given content.
Fault saveBytes(IStr path, IStr bytes);
/// Loads a text file and returns the contents as a list.
LStr loadText(IStr path);
/// Loads a text file into a temporary buffer for the current frame.
IStr loadTempText(IStr path, Sz capacity = defaultEngineLoadOrSaveTextCapacity);
/// Loads a text file into the given buffer.
Fault loadTextIntoBuffer(L = LStr)(IStr path, ref L listBuffer);
/// Saves a text file with the given content.
Fault saveText(IStr path, IStr text);
/// Saves an image taken from the given viewport.
Fault saveScreenshot(IStr path, ViewportId viewport, bool hasAlpha);
They use the assets path unless the input starts with / or \, or isUsingAssetsPath is false.
Path separators are also normalized to the platform's native format.
Additionally, engine resources are separated into two groups. Managed and temporary.
Managed Resources
Managed resources (TextureId, FontId, SoundId, ViewportId) are managed by the engine, meaning they get updated every frame when necessary (e.g. sounds) and can be safely shared throughout the code.
These resources use something known as generational indices.
Temporary Resources
Temporary resources are only valid for the duration of the current frame.
Embedding Resources
Resources can be embedded into the binary with D's import feature.
DUB projects already pass -J=assets to the compiler, so everything in the assets folder is available automatically. For example:
auto atlas = TextureId();
void ready() {
atlas = loadTexture(cast(ubyte[]) import("atlas.png"));
}
Frame Allocator
The engine provides a frame allocator for temporary memory. Allocations from it only live for the current frame and are automatically cleared at the end. This is useful for short-lived data such as strings or small objects that only need to exist for one frame.
/// Allocates raw memory from the frame arena.
void* frameMalloc(Sz alignment, Sz size);
/// Reallocates memory from the frame arena.
void* frameRealloc(Sz alignment, void* oldPtr, Sz oldSize, Sz newSize);
/// Allocates uninitialized memory for a single value of type `T`.
T* frameMakeBlank(T)();
/// Allocates and initializes a single value of type `T`.
T* frameMake(T)();
/// Allocates and initializes a single value of type `T`.
T* frameMake(T)(const(T) value);
/// Allocates uninitialized memory for an array of type `T` with the given length.
T[] frameMakeSliceBlank(T)(Sz length);
/// Allocates and initializes an array of type `T` with the given length.
T[] frameMakeSlice(T)(Sz length);
/// Allocates and initializes an array of type `T` with the given length.
T[] frameMakeSlice(T)(Sz length, const(T) value);
/// Allocates and initializes an array of type `T` with the given slice.
T[] frameMakeSlice(T)(const(T)[] values);
/// Resizes an array of type `T` with the given slice pointer and length.
T[] frameResizeSlice(T)(T* values, Sz oldLength, Sz newLength);
/// Returns a memory context from the frame allocator.
MemoryContext frameMemoryContext();
The engine uses this allocator internally for functions like loadTempText and prepareTempText.
Memory Tracking
Parin includes a lightweight memory tracking system that can detect leaks or invalid frees in debug builds. By default, leaks will be printed when the game ends only if they are detected.
/// Returns true if memory tracking logs are enabled.
bool isLoggingMemoryTrackingInfo();
/// Enables or disables memory tracking logs.
void setIsLoggingMemoryTrackingInfo(bool value, IStr pathFilter = "");
Example output:
Memory Leaks: 4 (total 699 bytes, 5 ignored)
1 leak, 20 bytes, source/app.d:24
1 leak, 53 bytes, source/app.d:31
2 leak, 32 bytes, source/app.d:123
The leak summary above can be filtered, showing only leaks with paths containing the filter string.
For example, setIsLoggingMemoryTrackingInfo(true, "app.d") shows only leaks with "app.d" in the path.
Specific allocations can be ignored with ignoreLeak like this:
// struct Game { int hp; int mp; }
// Game* game;
game = jokaMake!Game().ignoreLeak();
Allocations can also be grouped to make it easier to understand what each allocation is used for with ScopedAllocationGroup like this:
// This can also be done with the `beginAllocationGroup` and `endAllocationGroup` functions.
with (ScopedAllocationGroup("World")) {
allocateMonsters();
allocateActors();
with (ScopedAllocationGroup("Contents")) {
allocateItems();
allocateEvents();
}
}
allocateText(); // Not part of any group.
You can check whether memory tracking is active with static if (isTrackingMemory), and if it is, you can inspect the current tracking state via _memoryTrackingState.
_memoryTrackingState is thread-local, so each thread has its own separate tracking state.
This isn't strictly a Parin feature. It comes from Joka, the library Parin uses for memory allocations. Anything allocated through Joka is automatically tracked.
Debug Mode
Parin has a debug mode that toggles with the F3 key by default.
/// Returns true if debug mode is active.
bool isDebugMode();
/// Returns true when entering debug mode this frame.
bool isEnteringDebugMode();
/// Returns true when exiting debug mode this frame.
bool isExitingDebugMode();
/// Sets whether debug mode should be active
void setIsDebugMode(bool value);
/// Toggles the debug mode on or off.
void toggleIsDebugMode();
/// Sets the key that toggles debug mode.
void setDebugModeKey(Keyboard value);
Additionally, you can pass an inspect function to runGame.
When debug mode is on, this function runs after update and can be used for debug tools.
For example:
// It assumes you are using: `parin.addons.microui`
void inspect() {
beginUiFrame();
if (beginWindow("Window", 500, 80, 350, 370)) {
button("Hello");
endWindow();
}
endUiFrame();
}
mixin runGame!(ready, update, finish, 960, 540, "Parin", inspect);
Scheduling
A simple scheduling system exists for running functions later or at intervals.
This is useful for timers and background tasks.
Scheduled functions run before update.
/// Schedules a task to run every interval.
EngineTaskId repeatTask(UpdateFunc func, float interval, int count = -1, bool canCallNow = false);
/// Cancels a scheduled task by its ID.
void cancelTask(EngineTaskId id);
Example:
import parin;
auto text = "GNU!";
// A function (task) that will run every N seconds.
bool updateText(float dt) {
text ~= '!';
return false;
}
void ready() {
lockResolution(320, 180);
// Repeat this function every 5 seconds.
repeatTask(&updateText, 0.5);
}
bool update(float dt) {
drawText(text, Vec2(8));
return false;
}
mixin runGame!(ready, update, null);
CLI Flags
Every project by default can accept predefined CLI flags that toggle engine features on and off, or modify their behavior.
For example, vsync can be disabled by passing the -parin=vsyncOff argument.
The + character can be used to include multiple flags in one argument: -parin=vsyncOff+debugMode.
Below is a list of all the available flags:
struct EngineArgFlags {
bool vsyncOff; // Disables VSync.
bool vsyncOn; // Enables VSync.
bool debugMode; // Starts a project in debug mode.
bool largeWindow; // Opens a window that is 2X larger.
}
enum argPrefix = "-parin=";
enum argSep = "+";