Initial commit
This commit is contained in:
commit
138e946f34
19
.gitignore
vendored
Normal file
19
.gitignore
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
*.exe
|
||||
*.sublime-workspace
|
||||
*.pdb
|
||||
*.raddbgi
|
||||
*.raddbg
|
||||
*.dll
|
||||
*.exp
|
||||
*.rdi
|
||||
*.obj
|
||||
*.lib
|
||||
log.txt
|
||||
*.bin
|
||||
*.dylib
|
||||
*.so
|
||||
*.dSYM
|
||||
linux
|
||||
atlas.png
|
||||
pdbs/
|
||||
game_web/
|
62
.vscode/launch.json
vendored
Normal file
62
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
// Windows configs (only difference from linux/mac is "type" and "program")
|
||||
{
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "Build Hot Reload",
|
||||
"name": "Run Hot Reload (Windows)",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"program": "${workspaceFolder}/game_hot_reload.exe",
|
||||
},
|
||||
{
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "Build Debug",
|
||||
"name": "Run Debug (Windows)",
|
||||
"program": "${workspaceFolder}/game_debug.exe",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "cppvsdbg",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "Build Release",
|
||||
"name": "Run Release (Windows)",
|
||||
"program": "${workspaceFolder}/game_release.exe",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
|
||||
// Linux / Mac configs
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "Build Hot Reload",
|
||||
"name": "Run Hot Reload (Linux / Mac)",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"program": "${workspaceFolder}/game_hot_reload.bin",
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "Build Debug",
|
||||
"name": "Run Debug (Linux / Mac)",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"program": "${workspaceFolder}/game_debug.bin",
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "Build Release",
|
||||
"name": "Run Release (Linux / Mac)",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"program": "${workspaceFolder}/game_release.bin",
|
||||
},
|
||||
]
|
||||
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"debug.allowBreakpointsEverywhere": true,
|
||||
}
|
60
.vscode/tasks.json
vendored
Normal file
60
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"command": "",
|
||||
"args": [],
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Build Debug",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "${workspaceFolder}/build_debug.bat",
|
||||
},
|
||||
"linux": {
|
||||
"command": "${workspaceFolder}/build_debug.sh",
|
||||
},
|
||||
"osx": {
|
||||
"command": "${workspaceFolder}/build_debug.sh",
|
||||
},
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"label": "Build Release",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "${workspaceFolder}/build_release.bat",
|
||||
},
|
||||
"linux": {
|
||||
"command": "${workspaceFolder}/build_release.sh",
|
||||
},
|
||||
"osx": {
|
||||
"command": "${workspaceFolder}/build_release.sh",
|
||||
},
|
||||
"group": "build"
|
||||
},
|
||||
{
|
||||
"label": "Build Hot Reload",
|
||||
"type": "shell",
|
||||
"windows": {
|
||||
"command": "${workspaceFolder}/build_hot_reload.bat",
|
||||
},
|
||||
"linux": {
|
||||
"command": "${workspaceFolder}/build_hot_reload.sh",
|
||||
},
|
||||
"osx": {
|
||||
"command": "${workspaceFolder}/build_hot_reload.sh",
|
||||
},
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared",
|
||||
"showReuseMessage": false,
|
||||
"clear": true
|
||||
},
|
||||
"group": {
|
||||
"kind": "build",
|
||||
"isDefault": true
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
7
LICENSE
Normal file
7
LICENSE
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright (c) 2024 Karl Zylinski
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
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 OR COPYRIGHT HOLDERS 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.
|
83
README.md
Normal file
83
README.md
Normal file
@ -0,0 +1,83 @@
|
||||
# Odin + Raylib + Hot Reload template
|
||||
|
||||
This is an [Odin](https://github.com/odin-lang/Odin) + [Raylib](https://github.com/raysan5/raylib) game template with [Hot Reloading](http://zylinski.se/posts/hot-reload-gameplay-code/) pre-setup. It makes it possible to reload gameplay code while the game is running.
|
||||
|
||||
Supported platforms: Windows, macOS and Linux.
|
||||
|
||||
Supported editors and debuggers: [Sublime Text](#sublime-text), [VS Code](#vs-code) and [RAD Debugger](#rad-debugger).
|
||||
|
||||

|
||||
|
||||
I used this kind of hot reloading while developing my game [CAT & ONION](https://store.steampowered.com/app/2781210/CAT__ONION/).
|
||||
|
||||
## Quick start
|
||||
|
||||
If you are on Linux / macOS: Below, replace `.bat` with `.sh` and `.exe` with `.bin`.
|
||||
|
||||
1. Run `build_hot_reload.bat` to create `game_hot_reload.exe` and `game.dll`. Note: It expects odin compiler to be part of your PATH environment variable.
|
||||
2. Run `game_hot_reload.exe`, leave it running.
|
||||
3. Make changes to the gameplay code in `game/game.odin`. For example, change the line `rl.ClearBackground(rl.BLACK)` so that it instead uses `rl.BLUE`. Save the file.
|
||||
4. Run `build_hot_reload.bat`, it will recompile `game.dll`.
|
||||
5. The running `game_hot_reload.exe` will see that `game.dll` changed and reload it. But it will use the same `Game_Memory` (a struct defined in `game/game.odin`) as before. This will make the game use your new code without having to restart.
|
||||
|
||||
Note, in step 4: `build_hot_reload.bat` does not rebuild `game_hot_reload.exe`. It checks if `game_hot_reload.exe` is already running, and if it is, it avoid recompiling it, since it will be locked anyways.
|
||||
|
||||
## Description
|
||||
|
||||
`build_hot_reload.bat` will build `game.dll` from the odin code in the `game` folder. It will also build `game_hot_reload.exe` from the code in the folder `main_hot_reload`.
|
||||
|
||||
When you run `game_hot_reload.exe` it will load `game.dll` and start the game. In order to hot reload, make some changes to anything in the `game` folder and re-run `build_hot_reload.bat`.
|
||||
|
||||
`game_hot_reload.exe` will notice that `game.dll` changed and reload it. The state you wish to keep between reloads goes into the `Game_Memory` struct in `game/game.odin`.
|
||||
|
||||
There is also a `build_release.bat` file that makes a `game_release.exe` that does not have the hot reloading stuff, since you probably do not want that in the released version of your game. This means that the release version does not use `game.dll`, instead it imports the `game` folder as a normal Odin package.
|
||||
|
||||
`build_debug.bat` is like `build_release.bat` but makes a debuggable executable, in case you need to debug your non-hot-reload-exe.
|
||||
|
||||
## Sublime Text
|
||||
|
||||
For those who use Sublime Text there's a project file: `project.sublime-project`.
|
||||
|
||||
How to use:
|
||||
- Open the project file in sublime
|
||||
- Choose the build system `Main Menu -> Tools -> Build System -> Game template` (you can rename the build system by editing `project.sublime-project` manually)
|
||||
- Compile and run by pressing using F7 / Ctrl + B / Cmd + B
|
||||
- After you make code changes and want to hot reload, just hit F7 / Ctrl + B / Cmd + B again
|
||||
|
||||
## RAD Debugger
|
||||
You can hot reload while attached to [RAD Debugger](https://github.com/EpicGamesExt/raddebugger). Attach to your `game_hot_reload` executable, make code changes in your code editor and re-run the the `build_hot_reload` script to build and hot reload.
|
||||
|
||||
## VS Code
|
||||
|
||||
You can build, debug and hot reload from within VS Code. Open the template using `File -> Open Folder...`.
|
||||
|
||||
Requirements for debugging to work:
|
||||
- Windows: [C++ build tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools)
|
||||
- Linux / Mac: [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)
|
||||
|
||||
<img alt="Image showing how to start debugging session by selecting Build Hot Reload from the dropdown in the Run and Debug sidebar" src="https://github.com/user-attachments/assets/e62d710b-06f1-4833-bb2a-ab95527cf38c" width="50%" title="Start debugging session by chooing 'Run Hot Reload' and pressing the green arrow button">
|
||||
|
||||
Launch with `Run Hot Reload` launch task, see image above. After you make code changes press `Ctrl + Shift + B` to rebuild and hot reload.
|
||||
|
||||
## Windows Debugging hacks
|
||||
On Windows the degugging while hot reloading works by outputting a new PDB each time the game DLL is built. It cleans up the PDBs when you do a fresh start. See `build_hot_reload.bat` for details.
|
||||
|
||||
## Demo streams
|
||||
|
||||
Streams that start from this template:
|
||||
- CAR RACER prototype: https://www.youtube.com/watch?v=KVbHJ_CLdkA
|
||||
- "point & click" prototype: https://www.youtube.com/watch?v=iRvs1Xr1W6o
|
||||
- Metroidvania / platform prototype: https://www.youtube.com/watch?v=kIxEMchPc3Y
|
||||
- Top-down adventure prototype: https://www.youtube.com/watch?v=cl8EOjOaoXc
|
||||
|
||||
## Atlas builder
|
||||
|
||||
The template works nicely together with my [atlas builder](https://github.com/karl-zylinski/atlas-builder). The atlas builder can build an atlas texture from a folder of png or aseprite files. Using an atlas can drastically reduce the number of draw calls your game uses. There's an example in that repository on how to set it up. The atlas generation step can easily be integrated into the build `bat` / `sh` files such as `build_hot_reload.bat`
|
||||
|
||||
## Questions?
|
||||
|
||||
Ask questions in my gamedev Discord: https://discord.gg/4FsHgtBmFK
|
||||
|
||||
I have a blog post about Hot Reloading here: http://zylinski.se/posts/hot-reload-gameplay-code/
|
||||
|
||||
## Have a nice day! /Karl Zylinski
|
3
build_debug.bat
Normal file
3
build_debug.bat
Normal file
@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
|
||||
odin build main_release -out:game_debug.exe -strict-style -vet -debug
|
3
build_debug.sh
Executable file
3
build_debug.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
odin build main_release -out:game_debug.bin -strict-style -vet -debug
|
78
build_hot_reload.bat
Normal file
78
build_hot_reload.bat
Normal file
@ -0,0 +1,78 @@
|
||||
@echo off
|
||||
|
||||
set GAME_RUNNING=false
|
||||
set EXE=game_hot_reload.exe
|
||||
|
||||
:: Check if game is running
|
||||
FOR /F %%x IN ('tasklist /NH /FI "IMAGENAME eq %EXE%"') DO IF %%x == %EXE% set GAME_RUNNING=true
|
||||
|
||||
:: If game isn't running then:
|
||||
:: - delete all game_XXX.dll files
|
||||
:: - delete all PDBs in pdbs subdir
|
||||
:: - optionally create the pdbs subdir
|
||||
:: - write 0 into pdbs\pdb_number so game.dll PDBs start counting from zero
|
||||
::
|
||||
:: This makes sure we start over "fresh" at PDB number 0 when starting up the
|
||||
:: game and it also makes sure we don't have so many PDBs laying around.
|
||||
if %GAME_RUNNING% == false (
|
||||
del /q game_*.dll 2> nul
|
||||
|
||||
if exist "pdbs" (
|
||||
del /q pdbs\*.pdb
|
||||
) else (
|
||||
mkdir pdbs
|
||||
)
|
||||
|
||||
echo 0 > pdbs\pdb_number
|
||||
)
|
||||
|
||||
:: Load PDB number from file, increment and store back. For as long as the game
|
||||
:: is running the pdb_number file won't be reset to 0, so we'll get a PDB of a
|
||||
:: unique name on each hot reload.
|
||||
set /p PDB_NUMBER=<pdbs\pdb_number
|
||||
set /a PDB_NUMBER=%PDB_NUMBER%+1
|
||||
echo %PDB_NUMBER% > pdbs\pdb_number
|
||||
|
||||
:: Build game dll, use pdbs\game_%PDB_NUMBER%.pdb as PDB name so each dll gets
|
||||
:: its own PDB. This PDB stuff is done in order to make debugging work.
|
||||
:: Debuggers tend to lock PDBs or just misbehave if you reuse the same PDB while
|
||||
:: the debugger is attached. So each time we compile `game.dll` we give the
|
||||
:: PDB a unique PDB.
|
||||
::
|
||||
:: Note that we could not just rename the PDB after creation; the DLL contains a
|
||||
:: reference to where the PDB is.
|
||||
::
|
||||
:: Also note that we always write game.dll to the same file. game_hot_reload.exe
|
||||
:: monitors this file and does the hot reload when it changes.
|
||||
echo Building game.dll
|
||||
odin build game -strict-style -vet -debug -define:RAYLIB_SHARED=true -build-mode:dll -out:game.dll -pdb-name:pdbs\game_%PDB_NUMBER%.pdb > nul
|
||||
IF %ERRORLEVEL% NEQ 0 exit /b 1
|
||||
|
||||
:: If game.exe already running: Then only compile game.dll and exit cleanly
|
||||
if %GAME_RUNNING% == true (
|
||||
echo Game running, hot reloading... && exit /b 1
|
||||
)
|
||||
|
||||
:: Build game.exe, which starts the program and loads game.dll och does the logic for hot reloading.
|
||||
echo Building %EXE%
|
||||
odin build main_hot_reload -strict-style -vet -debug -out:%EXE%
|
||||
IF %ERRORLEVEL% NEQ 0 exit /b 1
|
||||
|
||||
:: Warning about raylib DLL not existing and where to find it.
|
||||
if exist "raylib.dll" (
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
:: Don't name this one ODIN_ROOT as the odin exe will start using that one then.
|
||||
set ODIN_PATH=
|
||||
|
||||
for /f %%i in ('odin root') do set "ODIN_PATH=%%i"
|
||||
|
||||
if exist "%ODIN_PATH%\vendor\raylib\windows\raylib.dll" (
|
||||
echo raylib.dll not found in current directory. Copying from %ODIN_PATH%\vendor\raylib\windows\raylib.dll
|
||||
copy "%ODIN_PATH%\vendor\raylib\windows\raylib.dll" .
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
echo "Please copy raylib.dll from <your_odin_compiler>/vendor/raylib/windows/raylib.dll to the same directory as game.exe"
|
||||
exit /b 1
|
51
build_hot_reload.sh
Executable file
51
build_hot_reload.sh
Executable file
@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# NOTE: this is a recent addition to the Odin compiler, if you don't have this command
|
||||
# you can change this to the path to the Odin folder that contains vendor, eg: "~/Odin".
|
||||
ROOT=$(odin root)
|
||||
if [ ! $? -eq 0 ]; then
|
||||
echo "Your Odin compiler does not have the 'odin root' command, please update or hardcode it in the script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -eu
|
||||
|
||||
# Figure out the mess that is dynamic libraries.
|
||||
case $(uname) in
|
||||
"Darwin")
|
||||
case $(uname -m) in
|
||||
"arm64") LIB_PATH="macos-arm64" ;;
|
||||
*) LIB_PATH="macos" ;;
|
||||
esac
|
||||
|
||||
DLL_EXT=".dylib"
|
||||
EXTRA_LINKER_FLAGS="-Wl,-rpath $ROOT/vendor/raylib/$LIB_PATH"
|
||||
;;
|
||||
*)
|
||||
DLL_EXT=".so"
|
||||
EXTRA_LINKER_FLAGS="'-Wl,-rpath=\$ORIGIN/linux'"
|
||||
|
||||
# Copy the linux libraries into the project automatically.
|
||||
if [ ! -d "linux" ]; then
|
||||
mkdir linux
|
||||
cp -r $ROOT/vendor/raylib/linux/libraylib*.so* linux
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# Build the game.
|
||||
echo "Building game$DLL_EXT"
|
||||
odin build game -extra-linker-flags:"$EXTRA_LINKER_FLAGS" -define:RAYLIB_SHARED=true -build-mode:dll -out:game_tmp$DLL_EXT -strict-style -vet -debug
|
||||
|
||||
# Need to use a temp file on Linux because it first writes an empty `game.so`, which the game will load before it is actually fully written.
|
||||
mv game_tmp$DLL_EXT game$DLL_EXT
|
||||
|
||||
# Do not build the game_hot_reload.bin if it is already running.
|
||||
# -f is there to make sure we match against full name, including .bin
|
||||
if pgrep -f game_hot_reload.bin > /dev/null; then
|
||||
echo "Game running, hot reloading..."
|
||||
exit 1
|
||||
else
|
||||
echo "Building game_hot_reload.bin"
|
||||
odin build main_hot_reload -out:game_hot_reload.bin -strict-style -vet -debug
|
||||
fi
|
3
build_release.bat
Normal file
3
build_release.bat
Normal file
@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
|
||||
odin build main_release -out:game_release.exe -strict-style -vet -no-bounds-check -o:speed -subsystem:windows
|
3
build_release.sh
Executable file
3
build_release.sh
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
odin build main_release -out:game_release.bin -strict-style -vet -no-bounds-check -o:speed
|
19
build_web.bat
Normal file
19
build_web.bat
Normal file
@ -0,0 +1,19 @@
|
||||
@echo off
|
||||
|
||||
set EMSCRIPTEN_SDK_DIR=c:\emsdk
|
||||
set OUT_DIR=game_web
|
||||
|
||||
if not exist %OUT_DIR% mkdir %OUT_DIR%
|
||||
|
||||
set EMSDK_QUIET=1
|
||||
call %EMSCRIPTEN_SDK_DIR%\emsdk_env.bat
|
||||
|
||||
odin build main_web -target:freestanding_wasm32 -build-mode:obj -define:RAYLIB_WASM_LIB=env.o -vet -strict-style -out:%OUT_DIR%/game
|
||||
IF %ERRORLEVEL% NEQ 0 exit /b 1
|
||||
|
||||
for /f %%i in ('odin root') do set "ODIN_PATH=%%i"
|
||||
|
||||
set files=main_web/main_web.c %OUT_DIR%/game.wasm.o %ODIN_PATH%\vendor\raylib\wasm\libraylib.a
|
||||
set flags=-sUSE_GLFW=3 -sASYNCIFY -sASSERTIONS -DPLATFORM_WEB
|
||||
set custom=--shell-file main_web/index_template.html
|
||||
emcc -o %OUT_DIR%/index.html %files% %flags% %custom% && del %OUT_DIR%\game.wasm.o
|
145
game/game.odin
Normal file
145
game/game.odin
Normal file
@ -0,0 +1,145 @@
|
||||
// This file is compiled as part of the `odin.dll` file. It contains the
|
||||
// procs that `game_hot_reload.exe` will call, such as:
|
||||
//
|
||||
// game_init: Sets up the game state
|
||||
// game_update: Run once per frame
|
||||
// game_shutdown: Shuts down game and frees memory
|
||||
// game_memory: Run just before a hot reload, so game.exe has a pointer to the
|
||||
// game's memory.
|
||||
// game_hot_reloaded: Run after a hot reload so that the `g_mem` global variable
|
||||
// can be set to whatever pointer it was in the old DLL.
|
||||
//
|
||||
// Note: When compiled as part of the release executable this whole package is imported as a normal
|
||||
// odin package instead of a DLL.
|
||||
|
||||
package game
|
||||
|
||||
import "core:math/linalg"
|
||||
import "core:fmt"
|
||||
import rl "vendor:raylib"
|
||||
|
||||
PIXEL_WINDOW_HEIGHT :: 180
|
||||
|
||||
Game_Memory :: struct {
|
||||
player_pos: rl.Vector2,
|
||||
some_number: int,
|
||||
}
|
||||
|
||||
g_mem: ^Game_Memory
|
||||
|
||||
game_camera :: proc() -> rl.Camera2D {
|
||||
w := f32(rl.GetScreenWidth())
|
||||
h := f32(rl.GetScreenHeight())
|
||||
|
||||
return {
|
||||
zoom = h/PIXEL_WINDOW_HEIGHT,
|
||||
target = g_mem.player_pos,
|
||||
offset = { w/2, h/2 },
|
||||
}
|
||||
}
|
||||
|
||||
ui_camera :: proc() -> rl.Camera2D {
|
||||
return {
|
||||
zoom = f32(rl.GetScreenHeight())/PIXEL_WINDOW_HEIGHT,
|
||||
}
|
||||
}
|
||||
|
||||
update :: proc() {
|
||||
input: rl.Vector2
|
||||
|
||||
if rl.IsKeyDown(.UP) || rl.IsKeyDown(.W) {
|
||||
input.y -= 1
|
||||
}
|
||||
if rl.IsKeyDown(.DOWN) || rl.IsKeyDown(.S) {
|
||||
input.y += 1
|
||||
}
|
||||
if rl.IsKeyDown(.LEFT) || rl.IsKeyDown(.A) {
|
||||
input.x -= 1
|
||||
}
|
||||
if rl.IsKeyDown(.RIGHT) || rl.IsKeyDown(.D) {
|
||||
input.x += 1
|
||||
}
|
||||
|
||||
input = linalg.normalize0(input)
|
||||
g_mem.player_pos += input * rl.GetFrameTime() * 100
|
||||
g_mem.some_number += 1
|
||||
}
|
||||
|
||||
draw :: proc() {
|
||||
rl.BeginDrawing()
|
||||
rl.ClearBackground(rl.BLACK)
|
||||
|
||||
rl.BeginMode2D(game_camera())
|
||||
rl.DrawRectangleV(g_mem.player_pos, {10, 20}, rl.WHITE)
|
||||
rl.DrawRectangleV({20, 20}, {10, 10}, rl.RED)
|
||||
rl.DrawRectangleV({-30, -20}, {10, 10}, rl.GREEN)
|
||||
rl.EndMode2D()
|
||||
|
||||
rl.BeginMode2D(ui_camera())
|
||||
// Note: main_hot_reload.odin clears the temp allocator at end of frame.
|
||||
rl.DrawText(fmt.ctprintf("some_number: %v\nplayer_pos: %v", g_mem.some_number, g_mem.player_pos), 5, 5, 8, rl.WHITE)
|
||||
rl.EndMode2D()
|
||||
|
||||
rl.EndDrawing()
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_update :: proc() -> bool {
|
||||
update()
|
||||
draw()
|
||||
return !rl.WindowShouldClose()
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_init_window :: proc() {
|
||||
rl.SetConfigFlags({.WINDOW_RESIZABLE, .VSYNC_HINT})
|
||||
rl.InitWindow(1280, 720, "Odin + Raylib + Hot Reload template!")
|
||||
rl.SetWindowPosition(200, 200)
|
||||
rl.SetTargetFPS(500)
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_init :: proc() {
|
||||
g_mem = new(Game_Memory)
|
||||
|
||||
g_mem^ = Game_Memory {
|
||||
some_number = 100,
|
||||
}
|
||||
|
||||
game_hot_reloaded(g_mem)
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_shutdown :: proc() {
|
||||
free(g_mem)
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_shutdown_window :: proc() {
|
||||
rl.CloseWindow()
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_memory :: proc() -> rawptr {
|
||||
return g_mem
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_memory_size :: proc() -> int {
|
||||
return size_of(Game_Memory)
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_hot_reloaded :: proc(mem: rawptr) {
|
||||
g_mem = (^Game_Memory)(mem)
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_force_reload :: proc() -> bool {
|
||||
return rl.IsKeyPressed(.F5)
|
||||
}
|
||||
|
||||
@(export)
|
||||
game_force_restart :: proc() -> bool {
|
||||
return rl.IsKeyPressed(.F6)
|
||||
}
|
224
main_hot_reload/main_hot_reload.odin
Normal file
224
main_hot_reload/main_hot_reload.odin
Normal file
@ -0,0 +1,224 @@
|
||||
// Development game exe. Loads game.dll and reloads it whenever it changes.
|
||||
|
||||
package main
|
||||
|
||||
import "core:dynlib"
|
||||
import "core:fmt"
|
||||
import "core:c/libc"
|
||||
import "core:os"
|
||||
import "core:log"
|
||||
import "core:mem"
|
||||
|
||||
when ODIN_OS == .Windows {
|
||||
DLL_EXT :: ".dll"
|
||||
} else when ODIN_OS == .Darwin {
|
||||
DLL_EXT :: ".dylib"
|
||||
} else {
|
||||
DLL_EXT :: ".so"
|
||||
}
|
||||
|
||||
// We copy the DLL because using it directly would lock it, which would prevent
|
||||
// the compiler from writing to it.
|
||||
copy_dll :: proc(to: string) -> bool {
|
||||
exit: i32
|
||||
when ODIN_OS == .Windows {
|
||||
exit = libc.system(fmt.ctprintf("copy game.dll {0}", to))
|
||||
} else {
|
||||
exit = libc.system(fmt.ctprintf("cp game" + DLL_EXT + " {0}", to))
|
||||
}
|
||||
|
||||
if exit != 0 {
|
||||
fmt.printfln("Failed to copy game" + DLL_EXT + " to {0}", to)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
Game_API :: struct {
|
||||
lib: dynlib.Library,
|
||||
init_window: proc(),
|
||||
init: proc(),
|
||||
update: proc() -> bool,
|
||||
shutdown: proc(),
|
||||
shutdown_window: proc(),
|
||||
memory: proc() -> rawptr,
|
||||
memory_size: proc() -> int,
|
||||
hot_reloaded: proc(mem: rawptr),
|
||||
force_reload: proc() -> bool,
|
||||
force_restart: proc() -> bool,
|
||||
modification_time: os.File_Time,
|
||||
api_version: int,
|
||||
}
|
||||
|
||||
load_game_api :: proc(api_version: int) -> (api: Game_API, ok: bool) {
|
||||
mod_time, mod_time_error := os.last_write_time_by_name("game" + DLL_EXT)
|
||||
if mod_time_error != os.ERROR_NONE {
|
||||
fmt.printfln(
|
||||
"Failed getting last write time of game" + DLL_EXT + ", error code: {1}",
|
||||
mod_time_error,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE: this needs to be a relative path for Linux to work.
|
||||
game_dll_name := fmt.tprintf("{0}game_{1}" + DLL_EXT, "./" when ODIN_OS != .Windows else "", api_version)
|
||||
copy_dll(game_dll_name) or_return
|
||||
|
||||
// This proc matches the names of the fields in Game_API to symbols in the
|
||||
// game DLL. It actually looks for symbols starting with `game_`, which is
|
||||
// why the argument `"game_"` is there.
|
||||
_, ok = dynlib.initialize_symbols(&api, game_dll_name, "game_", "lib")
|
||||
if !ok {
|
||||
fmt.printfln("Failed initializing symbols: {0}", dynlib.last_error())
|
||||
}
|
||||
|
||||
api.api_version = api_version
|
||||
api.modification_time = mod_time
|
||||
ok = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
unload_game_api :: proc(api: ^Game_API) {
|
||||
if api.lib != nil {
|
||||
if !dynlib.unload_library(api.lib) {
|
||||
fmt.printfln("Failed unloading lib: {0}", dynlib.last_error())
|
||||
}
|
||||
}
|
||||
|
||||
if os.remove(fmt.tprintf("game_{0}" + DLL_EXT, api.api_version)) != nil {
|
||||
fmt.printfln("Failed to remove game_{0}" + DLL_EXT + " copy", api.api_version)
|
||||
}
|
||||
}
|
||||
|
||||
main :: proc() {
|
||||
context.logger = log.create_console_logger()
|
||||
|
||||
default_allocator := context.allocator
|
||||
tracking_allocator: mem.Tracking_Allocator
|
||||
mem.tracking_allocator_init(&tracking_allocator, default_allocator)
|
||||
context.allocator = mem.tracking_allocator(&tracking_allocator)
|
||||
|
||||
reset_tracking_allocator :: proc(a: ^mem.Tracking_Allocator) -> bool {
|
||||
err := false
|
||||
|
||||
for _, value in a.allocation_map {
|
||||
fmt.printf("%v: Leaked %v bytes\n", value.location, value.size)
|
||||
err = true
|
||||
}
|
||||
|
||||
mem.tracking_allocator_clear(a)
|
||||
return err
|
||||
}
|
||||
|
||||
game_api_version := 0
|
||||
game_api, game_api_ok := load_game_api(game_api_version)
|
||||
|
||||
if !game_api_ok {
|
||||
fmt.println("Failed to load Game API")
|
||||
return
|
||||
}
|
||||
|
||||
game_api_version += 1
|
||||
game_api.init_window()
|
||||
game_api.init()
|
||||
|
||||
old_game_apis := make([dynamic]Game_API, default_allocator)
|
||||
|
||||
window_open := true
|
||||
for window_open {
|
||||
window_open = game_api.update()
|
||||
force_reload := game_api.force_reload()
|
||||
force_restart := game_api.force_restart()
|
||||
reload := force_reload || force_restart
|
||||
game_dll_mod, game_dll_mod_err := os.last_write_time_by_name("game" + DLL_EXT)
|
||||
|
||||
if game_dll_mod_err == os.ERROR_NONE && game_api.modification_time != game_dll_mod {
|
||||
reload = true
|
||||
}
|
||||
|
||||
if reload {
|
||||
new_game_api, new_game_api_ok := load_game_api(game_api_version)
|
||||
|
||||
if new_game_api_ok {
|
||||
force_restart = force_restart || game_api.memory_size() != new_game_api.memory_size()
|
||||
|
||||
if !force_restart {
|
||||
// This does the normal hot reload
|
||||
|
||||
// Note that we don't unload the old game APIs because that
|
||||
// would unload the DLL. The DLL can contain stored info
|
||||
// such as string literals. The old DLLs are only unloaded
|
||||
// on a full reset or on shutdown.
|
||||
append(&old_game_apis, game_api)
|
||||
game_memory := game_api.memory()
|
||||
game_api = new_game_api
|
||||
game_api.hot_reloaded(game_memory)
|
||||
} else {
|
||||
// This does a full reset. That's basically like opening and
|
||||
// closing the game, without having to restart the executable.
|
||||
//
|
||||
// You end up in here if the game requests a full reset OR
|
||||
// if the size of the game memory has changed. That would
|
||||
// probably lead to a crash anyways.
|
||||
|
||||
game_api.shutdown()
|
||||
reset_tracking_allocator(&tracking_allocator)
|
||||
|
||||
for &g in old_game_apis {
|
||||
unload_game_api(&g)
|
||||
}
|
||||
|
||||
clear(&old_game_apis)
|
||||
unload_game_api(&game_api)
|
||||
game_api = new_game_api
|
||||
game_api.init()
|
||||
}
|
||||
|
||||
game_api_version += 1
|
||||
}
|
||||
}
|
||||
|
||||
if len(tracking_allocator.bad_free_array) > 0 {
|
||||
for b in tracking_allocator.bad_free_array {
|
||||
log.errorf("Bad free at: %v", b.location)
|
||||
}
|
||||
|
||||
// This prevents the game from closing without you seeing the bad
|
||||
// frees. This is mostly needed because I use Sublime Text and my game's
|
||||
// console isn't hooked up into Sublime's console properly.
|
||||
libc.getchar()
|
||||
panic("Bad free detected")
|
||||
}
|
||||
|
||||
free_all(context.temp_allocator)
|
||||
}
|
||||
|
||||
free_all(context.temp_allocator)
|
||||
game_api.shutdown()
|
||||
if reset_tracking_allocator(&tracking_allocator) {
|
||||
// This prevents the game from closing without you seeing the memory
|
||||
// leaks. This is mostly needed because I use Sublime Text and my game's
|
||||
// console isn't hooked up into Sublime's console properly.
|
||||
libc.getchar()
|
||||
}
|
||||
|
||||
for &g in old_game_apis {
|
||||
unload_game_api(&g)
|
||||
}
|
||||
|
||||
delete(old_game_apis)
|
||||
|
||||
game_api.shutdown_window()
|
||||
unload_game_api(&game_api)
|
||||
mem.tracking_allocator_destroy(&tracking_allocator)
|
||||
}
|
||||
|
||||
// Make game use good GPU on laptops.
|
||||
|
||||
@(export)
|
||||
NvOptimusEnablement: u32 = 1
|
||||
|
||||
@(export)
|
||||
AmdPowerXpressRequestHighPerformance: i32 = 1
|
76
main_release/main_release.odin
Normal file
76
main_release/main_release.odin
Normal file
@ -0,0 +1,76 @@
|
||||
// For making a release exe that does not use hot reload.
|
||||
|
||||
package main_release
|
||||
|
||||
import "core:log"
|
||||
import "core:os"
|
||||
|
||||
import game "../game"
|
||||
|
||||
USE_TRACKING_ALLOCATOR :: #config(USE_TRACKING_ALLOCATOR, false)
|
||||
|
||||
main :: proc() {
|
||||
when USE_TRACKING_ALLOCATOR {
|
||||
default_allocator := context.allocator
|
||||
tracking_allocator: Tracking_Allocator
|
||||
tracking_allocator_init(&tracking_allocator, default_allocator)
|
||||
context.allocator = allocator_from_tracking_allocator(&tracking_allocator)
|
||||
}
|
||||
|
||||
mode: int = 0
|
||||
when ODIN_OS == .Linux || ODIN_OS == .Darwin {
|
||||
mode = os.S_IRUSR | os.S_IWUSR | os.S_IRGRP | os.S_IROTH
|
||||
}
|
||||
|
||||
logh, logh_err := os.open("log.txt", (os.O_CREATE | os.O_TRUNC | os.O_RDWR), mode)
|
||||
|
||||
if logh_err == os.ERROR_NONE {
|
||||
os.stdout = logh
|
||||
os.stderr = logh
|
||||
}
|
||||
|
||||
logger := logh_err == os.ERROR_NONE ? log.create_file_logger(logh) : log.create_console_logger()
|
||||
context.logger = logger
|
||||
|
||||
game.game_init_window()
|
||||
game.game_init()
|
||||
|
||||
window_open := true
|
||||
for window_open {
|
||||
window_open = game.game_update()
|
||||
|
||||
when USE_TRACKING_ALLOCATOR {
|
||||
for b in tracking_allocator.bad_free_array {
|
||||
log.error("Bad free at: %v", b.location)
|
||||
}
|
||||
|
||||
clear(&tracking_allocator.bad_free_array)
|
||||
}
|
||||
|
||||
free_all(context.temp_allocator)
|
||||
}
|
||||
|
||||
free_all(context.temp_allocator)
|
||||
game.game_shutdown()
|
||||
game.game_shutdown_window()
|
||||
|
||||
if logh_err == os.ERROR_NONE {
|
||||
log.destroy_file_logger(logger)
|
||||
}
|
||||
|
||||
when USE_TRACKING_ALLOCATOR {
|
||||
for key, value in tracking_allocator.allocation_map {
|
||||
log.error("%v: Leaked %v bytes\n", value.location, value.size)
|
||||
}
|
||||
|
||||
tracking_allocator_destroy(&tracking_allocator)
|
||||
}
|
||||
}
|
||||
|
||||
// make game use good GPU on laptops etc
|
||||
|
||||
@(export)
|
||||
NvOptimusEnablement: u32 = 1
|
||||
|
||||
@(export)
|
||||
AmdPowerXpressRequestHighPerformance: i32 = 1
|
78
main_web/index_template.html
Normal file
78
main_web/index_template.html
Normal file
@ -0,0 +1,78 @@
|
||||
<!doctype html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
|
||||
<title>Odin + Raylib on the web</title>
|
||||
|
||||
<meta name="title" content="Odin + Raylib on the web">
|
||||
<meta name="description" content="Make games using Odin + Raylib that work in the browser">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="shortcut icon" href="https://www.raylib.com/favicon.ico">
|
||||
|
||||
<!-- Web Style -->
|
||||
<style>
|
||||
body {
|
||||
margin: 0px;
|
||||
overflow: hidden;
|
||||
background-color: black;
|
||||
}
|
||||
canvas.emscripten {
|
||||
border: 0px none;
|
||||
background-color: black;}
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<script type='text/javascript' src="https://cdn.jsdelivr.net/gh/eligrey/FileSaver.js/dist/FileSaver.min.js"> </script>
|
||||
<script type='text/javascript'>
|
||||
function saveFileFromMEMFSToDisk(memoryFSname, localFSname) // This can be called by C/C++ code
|
||||
{
|
||||
var isSafari = false; // Not supported, navigator.userAgent access is being restricted
|
||||
//var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
||||
var data = FS.readFile(memoryFSname);
|
||||
var blob;
|
||||
|
||||
if (isSafari) blob = new Blob([data.buffer], { type: "application/octet-stream" });
|
||||
else blob = new Blob([data.buffer], { type: "application/octet-binary" });
|
||||
|
||||
// NOTE: SaveAsDialog is a browser setting. For example, in Google Chrome,
|
||||
// in Settings/Advanced/Downloads section you have a setting:
|
||||
// 'Ask where to save each file before downloading' - which you can set true/false.
|
||||
// If you enable this setting it would always ask you and bring the SaveAsDialog
|
||||
saveAs(blob, localFSname);
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<canvas class=emscripten id=canvas oncontextmenu=event.preventDefault() tabindex=-1></canvas>
|
||||
<p id="output" />
|
||||
<script>
|
||||
var Module = {
|
||||
print: (function() {
|
||||
var element = document.getElementById('output');
|
||||
if (element) element.value = ''; // clear browser cache
|
||||
return function(text) {
|
||||
if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
|
||||
console.log(text);
|
||||
if (element) {
|
||||
element.value += text + "\n";
|
||||
element.scrollTop = element.scrollHeight; // focus on bottom
|
||||
}
|
||||
};
|
||||
})(),
|
||||
canvas: (function() {
|
||||
var canvas = document.getElementById('canvas');
|
||||
return canvas;
|
||||
})()
|
||||
};
|
||||
</script>
|
||||
{{{ SCRIPT }}}
|
||||
</body>
|
||||
</html>
|
37
main_web/main_web.c
Normal file
37
main_web/main_web.c
Normal file
@ -0,0 +1,37 @@
|
||||
#include <stdlib.h>
|
||||
#include <emscripten/emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
|
||||
extern void web_init();
|
||||
extern void web_update();
|
||||
extern void web_window_size_changed(int w, int h);
|
||||
|
||||
void update_window_size() {
|
||||
double w, h;
|
||||
emscripten_get_element_css_size("#canvas", &w, &h);
|
||||
web_window_size_changed((int)w, (int)h);
|
||||
}
|
||||
|
||||
static EM_BOOL on_web_display_size_changed(
|
||||
int event_type,
|
||||
const EmscriptenUiEvent *event,
|
||||
void *user_data
|
||||
) {
|
||||
update_window_size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
emscripten_set_resize_callback(
|
||||
EMSCRIPTEN_EVENT_TARGET_WINDOW,
|
||||
0, 0, on_web_display_size_changed
|
||||
);
|
||||
|
||||
web_init();
|
||||
update_window_size();
|
||||
emscripten_set_main_loop(web_update, 0, 1);
|
||||
|
||||
// We don't really "shutdown" the game, since web browser tabs just close.
|
||||
|
||||
return 0;
|
||||
}
|
46
main_web/main_web.odin
Normal file
46
main_web/main_web.odin
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
These procs are the ones that will be called from `main_wasm.c`.
|
||||
*/
|
||||
|
||||
#+build wasm32, wasm64p32
|
||||
|
||||
package main_web
|
||||
|
||||
import "base:runtime"
|
||||
import "core:c"
|
||||
import "core:mem"
|
||||
import rl "vendor:raylib"
|
||||
import "../game"
|
||||
|
||||
@(private="file")
|
||||
wasm_context: runtime.Context
|
||||
|
||||
// I'm not sure @thread_local works with WASM. We'll see if anyone makes a
|
||||
// multi-threaded WASM game!
|
||||
@(private="file")
|
||||
@thread_local temp_allocator: WASM_Temp_Allocator
|
||||
|
||||
@export
|
||||
web_init :: proc "c" () {
|
||||
context = runtime.default_context()
|
||||
context.allocator = rl.MemAllocator()
|
||||
|
||||
wasm_temp_allocator_init(&temp_allocator, 1*mem.Megabyte)
|
||||
context.temp_allocator = wasm_temp_allocator(&temp_allocator)
|
||||
context.logger = create_wasm_logger()
|
||||
wasm_context = context
|
||||
|
||||
game.game_init_window()
|
||||
game.game_init()
|
||||
}
|
||||
|
||||
@export
|
||||
web_update :: proc "c" () {
|
||||
context = wasm_context
|
||||
game.game_update()
|
||||
}
|
||||
|
||||
@export
|
||||
web_window_size_changed :: proc "c" (w: c.int, h: c.int) {
|
||||
rl.SetWindowSize(w, h)
|
||||
}
|
120
main_web/main_web_support.odin
Normal file
120
main_web/main_web_support.odin
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
This file implements logger and temp allocator for the web build. The logger
|
||||
is based on the one found here: https://github.com/Aronicu/Raylib-WASM/tree/main
|
||||
*/
|
||||
|
||||
#+build wasm32, wasm64p32
|
||||
|
||||
package main_web
|
||||
|
||||
import "base:runtime"
|
||||
import "core:c"
|
||||
import "core:fmt"
|
||||
import "core:log"
|
||||
import "core:strings"
|
||||
|
||||
|
||||
// WASM logger
|
||||
|
||||
WASM_Logger_Opts :: log.Options{.Level, .Short_File_Path, .Line}
|
||||
|
||||
create_wasm_logger :: proc (lowest := log.Level.Debug, opt := WASM_Logger_Opts) -> log.Logger {
|
||||
return log.Logger{data = nil, procedure = wasm_logger_proc, lowest_level = lowest, options = opt}
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
wasm_logger_proc :: proc(
|
||||
logger_data: rawptr,
|
||||
level: log.Level,
|
||||
text: string,
|
||||
options: log.Options,
|
||||
location := #caller_location
|
||||
) {
|
||||
b := strings.builder_make(context.temp_allocator)
|
||||
strings.write_string(&b, Wasm_Logger_Level_Headers[level])
|
||||
do_location_header(options, &b, location)
|
||||
fmt.sbprint(&b, text)
|
||||
puts(strings.to_cstring(&b))
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
Wasm_Logger_Level_Headers := [?]string {
|
||||
0 ..< 10 = "[DEBUG] --- ",
|
||||
10 ..< 20 = "[INFO ] --- ",
|
||||
20 ..< 30 = "[WARN ] --- ",
|
||||
30 ..< 40 = "[ERROR] --- ",
|
||||
40 ..< 50 = "[FATAL] --- ",
|
||||
}
|
||||
|
||||
@(private="file")
|
||||
do_location_header :: proc(opts: log.Options, buf: ^strings.Builder, location := #caller_location) {
|
||||
if log.Location_Header_Opts & opts == nil {
|
||||
return
|
||||
}
|
||||
fmt.sbprint(buf, "[")
|
||||
file := location.file_path
|
||||
if .Short_File_Path in opts {
|
||||
last := 0
|
||||
for r, i in location.file_path {
|
||||
if r == '/' {
|
||||
last = i + 1
|
||||
}
|
||||
}
|
||||
file = location.file_path[last:]
|
||||
}
|
||||
|
||||
if log.Location_File_Opts & opts != nil {
|
||||
fmt.sbprint(buf, file)
|
||||
}
|
||||
if .Line in opts {
|
||||
if log.Location_File_Opts & opts != nil {
|
||||
fmt.sbprint(buf, ":")
|
||||
}
|
||||
fmt.sbprint(buf, location.line)
|
||||
}
|
||||
|
||||
if .Procedure in opts {
|
||||
if (log.Location_File_Opts | {.Line}) & opts != nil {
|
||||
fmt.sbprint(buf, ":")
|
||||
}
|
||||
fmt.sbprintf(buf, "%s()", location.procedure)
|
||||
}
|
||||
|
||||
fmt.sbprint(buf, "] ")
|
||||
}
|
||||
|
||||
@(default_calling_convention = "c")
|
||||
foreign {
|
||||
puts :: proc(buffer: cstring) -> c.int ---
|
||||
}
|
||||
|
||||
|
||||
// Temp Allocator
|
||||
// More or less a copy from base:runtime (that one is disabled in freestanding
|
||||
// build mode).
|
||||
|
||||
WASM_Temp_Allocator :: struct {
|
||||
arena: runtime.Arena,
|
||||
}
|
||||
|
||||
wasm_temp_allocator_init :: proc(s: ^WASM_Temp_Allocator, size: int, backing_allocator := context.allocator) {
|
||||
_ = runtime.arena_init(&s.arena, uint(size), backing_allocator)
|
||||
}
|
||||
|
||||
wasm_temp_allocator_proc :: proc(
|
||||
allocator_data: rawptr,
|
||||
mode: runtime.Allocator_Mode,
|
||||
size, alignment: int,
|
||||
old_memory: rawptr,
|
||||
old_size: int,
|
||||
loc := #caller_location) -> (data: []byte, err: runtime.Allocator_Error) {
|
||||
s := (^WASM_Temp_Allocator)(allocator_data)
|
||||
return runtime.arena_allocator_proc(&s.arena, mode, size, alignment, old_memory, old_size, loc)
|
||||
}
|
||||
|
||||
wasm_temp_allocator :: proc(allocator: ^WASM_Temp_Allocator) -> runtime.Allocator {
|
||||
return runtime.Allocator{
|
||||
procedure = wasm_temp_allocator_proc,
|
||||
data = allocator,
|
||||
}
|
||||
}
|
37
project.sublime-project
Normal file
37
project.sublime-project
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"folders":
|
||||
[
|
||||
{
|
||||
"path": ".",
|
||||
}
|
||||
],
|
||||
"build_systems":
|
||||
[
|
||||
{
|
||||
"file_regex": "^(.+)\\(([0-9]+):([0-9]+)\\) (.+)$",
|
||||
"name": "Game template",
|
||||
"windows": {
|
||||
"working_dir": "$project_path",
|
||||
"shell_cmd": "build_hot_reload.bat && start game_hot_reload.exe",
|
||||
},
|
||||
"osx": {
|
||||
"working_dir": "$project_path",
|
||||
"shell_cmd": "./build_hot_reload.sh && ./game_hot_reload.bin &",
|
||||
},
|
||||
"linux": {
|
||||
"working_dir": "$project_path",
|
||||
"shell_cmd": "./build_hot_reload.sh && ./game_hot_reload.bin &",
|
||||
}
|
||||
}
|
||||
],
|
||||
"settings":
|
||||
{
|
||||
"LSP":
|
||||
{
|
||||
"odin":
|
||||
{
|
||||
"enabled": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user