Compare commits

...

9 Commits

20 changed files with 1446 additions and 1013 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

View File

@ -1,81 +1,104 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.10" tiledversion="1.12.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="64" tileheight="64" infinite="0" nextlayerid="6" nextobjectid="4">
<map version="1.10" tiledversion="1.12.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="64" tileheight="64" infinite="1" nextlayerid="8" nextobjectid="19">
<tileset firstgid="1" source="tileset.tsx"/>
<layer id="2" name="Ground" width="30" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,90,90,90,90,90,90,90,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,90,90,90,90,90,90,90,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,90,90,90,90,90,90,90,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,90,90,90,90,90,90,90,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,90,90,90,90,90,90,90,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,90,90,90,90,90,90,90,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,90,90,90,90,90,90,90,90,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
<chunk x="0" y="0" width="16" height="16">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,90,90,90,90,90,90,90,90,0,0,
0,0,0,0,0,0,90,90,90,90,90,90,90,90,0,0,
0,0,0,0,0,0,90,90,90,90,90,90,90,90,0,0,
0,0,0,0,0,0,90,90,90,90,90,90,90,90,0,0,
0,0,0,0,0,0,90,90,90,90,90,90,90,90,0,0,
0,0,0,0,0,0,90,90,90,90,90,90,90,90,0,0,
0,0,0,0,0,0,90,90,90,90,90,90,90,90,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>
<chunk x="16" y="0" width="16" height="16">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,90,90,90,90,90,90,90,0,0,0,0,0,
0,0,0,0,90,90,90,90,90,90,90,0,0,0,0,0,
0,0,0,0,90,90,90,90,90,90,90,0,0,0,0,0,
0,0,0,0,90,90,90,90,90,90,90,0,0,0,0,0,
0,0,0,0,90,90,90,90,90,90,90,0,0,0,0,0,
0,0,0,0,90,90,90,90,90,90,90,0,0,0,0,0,
0,0,0,0,90,90,90,90,90,90,90,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>
</data>
</layer>
<layer id="1" name="Walls" width="30" height="20">
<layer id="1" name="Foreground" width="30" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,98,98,101,98,98,98,98,98,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,98,0,0,98,0,0,0,98,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,98,0,0,0,0,0,0,98,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,98,0,0,0,0,0,0,98,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,98,0,0,0,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,98,0,0,98,0,0,0,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,98,98,98,98,98,101,101,101,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
</layer>
<layer id="4" name="Boxes" width="30" height="20">
<data encoding="csv">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,7,0,0,7,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,7,0,9,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</data>
<chunk x="0" y="0" width="16" height="16">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,98,98,101,98,98,98,98,98,0,0,
0,0,0,0,0,0,98,0,0,98,0,0,0,98,0,0,
0,0,0,0,0,0,98,0,0,0,0,0,0,98,0,0,
0,0,0,0,0,0,98,0,7,0,0,26,0,98,0,0,
0,0,0,0,0,0,98,0,0,0,0,0,0,101,0,0,
0,0,0,0,0,0,98,0,0,98,0,0,0,101,0,0,
0,0,0,0,0,0,98,98,98,98,98,101,101,101,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>
<chunk x="16" y="0" width="16" height="16">
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,98,98,98,98,98,98,98,0,0,0,0,0,
0,0,0,0,98,0,0,0,0,0,98,0,0,0,0,0,
0,0,0,0,98,0,7,0,26,0,98,0,0,0,0,0,
0,0,0,0,98,0,0,0,0,0,98,0,0,0,0,0,
0,0,0,0,98,0,7,0,26,0,98,0,0,0,0,0,
0,0,0,0,98,0,0,0,0,0,98,0,0,0,0,0,
0,0,0,0,98,98,98,98,98,98,98,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
</chunk>
</data>
</layer>
<objectgroup id="5" name="Markers">
<object id="1" name="spawn" x="479" y="550">
<object id="1" name="spawn" x="484" y="555">
<point/>
</object>
<object id="17" name="spawn" x="1381.33" y="497.333">
<point/>
</object>
</objectgroup>
<objectgroup id="7" name="Level bounds">
<object id="15" x="324" y="214.667" width="633.333" height="566.667">
<properties>
<property name="level" type="int" value="1"/>
</properties>
</object>
<object id="18" x="1196.67" y="206" width="633.333" height="566.667">
<properties>
<property name="level" type="int" value="2"/>
</properties>
</object>
</objectgroup>
</map>

View File

@ -13,7 +13,8 @@
}
},
"tileset.tsx": {
"scaleInDock": 1
"scaleInDock": 0.5,
"scaleInEditor": 1
}
},
"last.imagePath": "/home/rokas/code/games/sokol-template-v2/assets/sokoban",
@ -24,7 +25,9 @@
"map.tmx"
],
"project": "sokoban.tiled-project",
"property.type": "int",
"recentFiles": [
"tileset.tsx",
"map.tmx"
],
"tileset.lastUsedFormat": "tsx",

View File

@ -1,4 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset version="1.10" tiledversion="1.12.2" name="tileset" tilewidth="64" tileheight="64" tilecount="104" columns="13">
<image source="../sokoban/sokoban_tilesheet.png" width="832" height="512"/>
<tile id="6">
<properties>
<property name="type" value="box"/>
</properties>
</tile>
<tile id="7">
<properties>
<property name="type" value="box"/>
</properties>
</tile>
<tile id="8">
<properties>
<property name="type" value="box"/>
</properties>
</tile>
<tile id="9">
<properties>
<property name="type" value="box"/>
</properties>
</tile>
<tile id="10">
<properties>
<property name="type" value="box"/>
</properties>
</tile>
<tile id="25">
<properties>
<property name="type" value="goal_marker"/>
</properties>
</tile>
<tile id="38">
<properties>
<property name="type" value="goal_marker"/>
</properties>
</tile>
<tile id="51">
<properties>
<property name="type" value="goal_marker"/>
</properties>
</tile>
<tile id="64">
<properties>
<property name="type" value="goal_marker"/>
</properties>
</tile>
<tile id="77">
<properties>
<property name="type" value="goal_marker"/>
</properties>
</tile>
<tile id="97">
<properties>
<property name="type" value="wall"/>
</properties>
</tile>
<tile id="98">
<properties>
<property name="type" value="wall"/>
</properties>
</tile>
<tile id="99">
<properties>
<property name="type" value="wall"/>
</properties>
</tile>
<tile id="100">
<properties>
<property name="type" value="wall"/>
</properties>
</tile>
</tileset>

View File

@ -1,10 +1,13 @@
const std = @import("std");
const Build = std.Build;
const sokol = @import("sokol");
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const name = "sokol_template_v2";
const assets_dir = "assets";
const asset_files = [_][]const u8{
"sokoban/sokoban_tilesheet.png",
@ -34,29 +37,39 @@ pub fn build(b: *std.Build) !void {
.optimize = optimize,
});
const dep_tiled = b.dependency("tiled", .{});
exe_mod.addImport("tiled", dep_tiled.module("tiled"));
const dep_stb = b.dependency("stb", .{});
exe_mod.addImport("stb_image", dep_stb.module("stb_image"));
exe_mod.addImport("stb_truetype", dep_stb.module("stb_truetype"));
var dep_sokol: *Build.Dependency = undefined;
const platform_mod = b.createModule(.{
.root_source_file = b.path("src/platform/root.zig")
});
exe_mod.addImport("platform", platform_mod);
{
const build_options = b.addOptions();
build_options.addOption(bool, "has_imgui", has_imgui);
build_options.addOption(?[]const u8, "assets_dir", if (assets_from_filesystem) asset_dir_realpath else null);
exe_mod.addOptions("build_options", build_options);
platform_mod.addOptions("build_options", build_options);
exe_mod.addImport("embedded_assets", try createEmbeddedAssets(b, assets_dir, &asset_files));
platform_mod.addImport("embedded_assets", try createEmbeddedAssets(b, assets_dir, &asset_files));
const math_mod = b.createModule(.{
.root_source_file = b.path("src/platform/math.zig")
});
exe_mod.addImport("math", math_mod);
platform_mod.addImport("math", math_mod);
const dep_tiled = b.dependency("tiled", .{});
exe_mod.addImport("tiled", dep_tiled.module("tiled"));
const dep_sokol = b.dependency("sokol", .{
dep_sokol = b.dependency("sokol", .{
.target = target,
.optimize = optimize,
.with_sokol_imgui = has_imgui
});
const mod_sokol = dep_sokol.module("sokol");
exe_mod.linkLibrary(dep_sokol.artifact("sokol_clib"));
exe_mod.addImport("sokol", mod_sokol);
platform_mod.linkLibrary(dep_sokol.artifact("sokol_clib"));
platform_mod.addImport("sokol", mod_sokol);
const dep_shdc = dep_sokol.builder.dependency("shdc", .{});
@ -76,7 +89,7 @@ pub fn build(b: *std.Build) !void {
.slang = slang
});
mod_shader.addImport("math", math_mod);
exe_mod.addImport("shader", mod_shader);
platform_mod.addImport("shader", mod_shader);
if (has_imgui) {
if (b.lazyDependency("cimgui", .{
@ -85,16 +98,13 @@ pub fn build(b: *std.Build) !void {
})) |dep_cimgui| {
const cimgui = b.lazyImport(@This(), "cimgui").?;
const cimgui_conf = cimgui.getConfig(true);
exe_mod.addImport("cimgui", dep_cimgui.module(cimgui_conf.module_name));
platform_mod.addImport("cimgui", dep_cimgui.module(cimgui_conf.module_name));
dep_sokol.artifact("sokol_clib").root_module.addIncludePath(dep_cimgui.path(cimgui_conf.include_dir));
}
}
const dep_stb = b.dependency("stb", .{});
exe_mod.addImport("stb_image", dep_stb.module("stb_image"));
exe_mod.addImport("stb_vorbis", dep_stb.module("stb_vorbis"));
exe_mod.addImport("stb_rect_pack", dep_stb.module("stb_rect_pack"));
exe_mod.addImport("stb_truetype", dep_stb.module("stb_truetype"));
platform_mod.addImport("stb_vorbis", dep_stb.module("stb_vorbis"));
platform_mod.addImport("stb_rect_pack", dep_stb.module("stb_rect_pack"));
const dep_tracy = b.dependency("tracy", .{
.target = target,
@ -103,32 +113,31 @@ pub fn build(b: *std.Build) !void {
.tracy_only_localhost = true
});
if (has_tracy) {
exe_mod.linkLibrary(dep_tracy.artifact("tracy"));
platform_mod.linkLibrary(dep_tracy.artifact("tracy"));
}
platform_mod.addImport("tracy", dep_tracy.module("tracy"));
}
exe_mod.addImport("tracy", dep_tracy.module("tracy"));
var run_cmd_step: *std.Build.Step = undefined;
if (is_wasm) {
if (true) {
@panic("TODO: test in 0.17");
}
@panic("TODO: Test wasm support on 0.17");
const build_step = buildWasm(b, .{
.name = "index",
.root_module = exe_mod,
.dep_sokol = dep_sokol,
});
b.getInstallStep().dependOn(build_step);
const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{});
const emrun_step = sokol.emRunStep(b, .{ .name = "index", .emsdk = dep_emsdk });
emrun_step.step.dependOn(build_step);
run_cmd_step = &emrun_step.step;
// const build_step = buildWasm(b, .{
// .name = "index",
// .root_module = exe_mod,
// .dep_sokol = dep_sokol,
// });
// b.getInstallStep().dependOn(build_step);
//
// const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{});
// const emrun_step = sokol.emRunStep(b, .{ .name = "index", .emsdk = dep_emsdk });
// emrun_step.step.dependOn(build_step);
// run_cmd_step = &emrun_step.step;
// TODO: Create a zip archive of all of the files. Would be useful for easier itch.io upload
} else {
const exe = buildNative(b, .{
.name = "sokol_template_v2",
.name = name,
.root_module = exe_mod
});
b.installArtifact(exe);

View File

@ -2,11 +2,15 @@
.name = .sokol_template_v2,
.version = "0.0.0",
.fingerprint = 0xf440b4e6e51f5206, // Changing this has security and trust implications.
.minimum_zig_version = "0.16.0",
.dependencies = .{
.sokol = .{
.url = "git+https://github.com/floooh/sokol-zig.git#ba97ac4e82e36a0f6c62f7fad5dc9c2d82beadd8",
.hash = "sokol-0.1.0-pb1HK4prNwDGx2RJFyv7VtYJubDahi19XKioPzPeMCxv",
.url = "git+https://github.com/floooh/sokol-zig.git#ff40f04d4f51c73b2bf460d87fd2d7aa0dedc22f",
.hash = "sokol-0.1.0-pb1HKygbQQCINV-qSe4SFm92QWaT_95VpWTJkwYqzOrX",
},
.cimgui = .{
.url = "git+https://github.com/floooh/dcimgui.git#e2f0e0d93adec02743c55940be23ffe286e857f7",
.hash = "cimgui-0.1.0-44ClkR8dmgDqqnfloyLE1bUAtl4zSKTT1wxaHSEC0ikV",
.lazy = true
},
.stb = .{
.path = "./libs/stb",
@ -17,11 +21,6 @@
.tracy = .{
.path = "./libs/tracy",
},
.cimgui = .{
.url = "git+https://github.com/floooh/dcimgui.git#3c2b41a9333b1195e2c2f8ce6a2d3e50660a73bf",
.hash = "cimgui-0.1.0-44ClkR8dmgDqqnfloyLE1bUAtl4zSKTT1wxaHSEC0ikV",
.lazy = true
},
},
.paths = .{
"build.zig",

View File

@ -1,4 +1,5 @@
const std = @import("std");
const assert = std.debug.assert;
const Property = @import("./property.zig");
const xml = @import("./xml.zig");
@ -9,43 +10,106 @@ const Position = @import("./position.zig");
const Layer = @This();
pub const TileVariant = struct {
width: u32,
height: u32,
chunks: []Chunk,
const Chunk = struct {
x: i32,
y: i32,
width: u32,
height: u32,
data: []u32,
};
fn initDataFromXml(
arena: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
lexer: *xml.Lexer,
) ![]u32 {
var iter = xml.TagParser.init(lexer);
const attrs = try iter.begin("data");
fn parseCSVData(arena: std.mem.Allocator, csv: []const u8) ![]u32 {
const cell_count = std.mem.countScalar(u8, csv, ',')+1;
var cells: std.ArrayList(u32) = try .initCapacity(arena, cell_count);
const encoding = attrs.get("encoding") orelse "csv";
// TODO: compression
var temp_tiles: std.ArrayList(u32) = .empty;
if (std.mem.eql(u8, encoding, "csv")) {
const text = try lexer.nextExpectText();
var split_iter = std.mem.splitScalar(u8, text, ',');
var split_iter = std.mem.splitScalar(u8, csv, ',');
while (split_iter.next()) |raw_tile_id| {
const tile_id_str = std.mem.trim(u8, raw_tile_id, &std.ascii.whitespace);
const tile_id = try std.fmt.parseInt(u32, tile_id_str, 10);
try temp_tiles.append(scratch.allocator(), tile_id);
cells.appendAssumeCapacity(tile_id);
}
return cells.items;
}
fn initFromXml(
arena: std.mem.Allocator,
doc: xml.Document,
element_id: xml.Element.Id,
) !TileVariant {
const tag = try doc.getTagExpect(element_id, "layer");
const attrs = tag.variant.tag.attributes;
const layer_width = try attrs.getNumber(u32, "width") orelse return error.MissingWidth;
const layer_height = try attrs.getNumber(u32, "height") orelse return error.MissingHeight;
const data_id = tag.tree.first_child;
var data_element = try doc.getTagExpect(data_id, "data");
const encoding = data_element.variant.tag.attributes.get("encoding") orelse "csv";
// TODO: compression
var chunks: std.ArrayList(Chunk) = .empty;
if (std.mem.eql(u8, encoding, "csv")) {
try chunks.ensureTotalCapacityPrecise(arena, doc.countChildren(data_id));
var iter = doc.iterChildren(data_id);
while (iter.next()) |child_id| {
const child = doc.get(child_id).?;
if (child.variant == .text) {
const data = try parseCSVData(arena, child.variant.text);
assert(data.len == layer_width * layer_height);
chunks.appendAssumeCapacity(Chunk{
.data = data,
.x = 0,
.y = 0,
.width = layer_width,
.height = layer_height,
});
} else if (child.variant == .tag and std.mem.eql(u8, child.variant.tag.name, "chunk")) {
const chunk_attrs = child.variant.tag.attributes;
const x = try chunk_attrs.getNumber(i32, "x") orelse return error.MissingWidth;
const y = try chunk_attrs.getNumber(i32, "y") orelse return error.MissingWidth;
const width = try chunk_attrs.getNumber(u32, "width") orelse return error.MissingWidth;
const height = try chunk_attrs.getNumber(u32, "height") orelse return error.MissingHeight;
const csv_text = doc.get(child.tree.first_child).?.variant.text; // TODO: Add checks
const data = try parseCSVData(arena, csv_text);
assert(data.len == width * height);
chunks.appendAssumeCapacity(Chunk{
.data = data,
.x = x,
.y = y,
.width = width,
.height = height,
});
}
}
} else {
return error.UnknownEncodingType;
}
try iter.finish("data");
return try arena.dupe(u32, temp_tiles.items);
return TileVariant{
.width = layer_width,
.height = layer_height,
.chunks = chunks.items
};
}
pub fn get(self: TileVariant, x: usize, y: usize) ?u32 {
if ((0 <= x and x < self.width) and (0 <= y and y < self.height)) {
return self.data[y * self.width + x];
pub fn get(self: TileVariant, x: i32, y: i32) ?u32 {
for (self.chunks) |chunk| {
const ox = x - chunk.x;
const oy = y - chunk.y;
if ((0 <= ox and ox < chunk.width) and (0 <= oy and oy < chunk.height)) {
return chunk.data[@as(u32, @intCast(oy)) * chunk.width + @as(u32, @intCast(ox))];
}
}
return null;
}
@ -59,9 +123,9 @@ pub const ImageVariant = struct {
width: ?u32,
height: ?u32,
fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Image {
var iter = xml.TagParser.init(lexer);
const attrs = try iter.begin("image");
fn initFromXml(arena: std.mem.Allocator, doc: xml.Document, element_id: xml.Element.Id) !Image {
const tag = try doc.getTagExpect(element_id, "image");
const attrs = tag.variant.tag.attributes;
// TODO: format
const source = try attrs.getDupe(arena, "source") orelse return error.MissingSource;
@ -69,8 +133,6 @@ pub const ImageVariant = struct {
const height = try attrs.getNumber(u32, "height") orelse null;
const transparent_color = try attrs.getColor("trans", false);
try iter.finish("image");
return Image{
.source = source,
.transparent_color = transparent_color,
@ -126,15 +188,6 @@ pub const Type = enum {
.{ "imagelayer", .image },
.{ "group", .group }
});
fn toXmlName(self: Type) []const u8 {
return switch (self) {
.tile => "layer",
.object => "objectgroup",
.image => "imagelayer",
.group => "group",
};
}
};
pub const Variant = union(Type) {
@ -159,16 +212,13 @@ variant: Variant,
pub fn initFromXml(
arena: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
lexer: *xml.Lexer,
doc: xml.Document,
element_id: xml.Element.Id
) !Layer {
const value = try lexer.peek() orelse return error.MissingStartTag;
if (value != .start_tag) return error.MissingStartTag;
const tag = doc.getTag(element_id) orelse return error.TagNotFound;
var layer_type = Type.name_map.get(value.start_tag.name) orelse return error.UnknownLayerType;
var iter = xml.TagParser.init(lexer);
const attrs = try iter.begin(layer_type.toXmlName());
const layer_type = Type.name_map.get(tag.variant.tag.name) orelse return error.UnknownLayerType;
const attrs = tag.variant.tag.attributes;
const id = try attrs.getNumber(u32, "id") orelse return error.MissingId;
const name = try attrs.getDupe(arena, "name") orelse "";
@ -184,14 +234,12 @@ pub fn initFromXml(
var variant: Variant = undefined;
switch (layer_type) {
.tile => {
const width = try attrs.getNumber(u32, "width") orelse return error.MissingWidth;
const height = try attrs.getNumber(u32, "height") orelse return error.MissingHeight;
variant = .{
.tile = TileVariant{
.width = width,
.height = height,
.data = &[0]u32{}
}
variant = Variant{
.tile = try TileVariant.initFromXml(
arena,
doc,
element_id
)
};
},
.image => {
@ -230,45 +278,41 @@ pub fn initFromXml(
var objects: std.ArrayList(Object) = .empty;
var layers: std.ArrayList(Layer) = .empty;
while (try iter.next()) |node| {
if (node.isTag("properties")) {
properties = try Property.List.initFromXml(arena, scratch, lexer);
continue;
}
if (variant == .tile and node.isTag("data")) {
variant.tile.data = try TileVariant.initDataFromXml(arena, scratch, lexer);
continue;
}
if (variant == .image and node.isTag("image")) {
variant.image.image = try ImageVariant.Image.initFromXml(arena, lexer);
continue;
}
if (variant == .object and node.isTag("object")) {
const object = try Object.initFromXml(arena, scratch, lexer);
try objects.append(scratch.allocator(), object);
continue;
}
if (variant == .group and isLayerNode(node)) {
const layer = try initFromXml(arena, scratch, lexer);
try layers.append(scratch.allocator(), layer);
continue;
}
try iter.skip();
}
try iter.finish(layer_type.toXmlName());
if (variant == .object) {
variant.object.items = try arena.dupe(Object, objects.items);
try objects.ensureTotalCapacityPrecise(arena, doc.countChildren(element_id));
}
if (variant == .group) {
variant.group.layers = try arena.dupe(Layer, layers.items);
try layers.ensureTotalCapacityPrecise(arena, doc.countChildren(element_id));
}
var iter = doc.iterChildren(element_id);
while (iter.next()) |child_id| {
const child = doc.getTag(child_id) orelse continue;
const child_name = child.variant.tag.name;
if (std.mem.eql(u8, child_name, "properties")) {
properties = try Property.List.initFromXml(arena, doc, child_id);
} else if (variant == .image and std.mem.eql(u8, child_name, "image")) {
variant.image.image = try ImageVariant.Image.initFromXml(arena, doc, child_id);
} else if (variant == .object and std.mem.eql(u8, child_name, "object")) {
const object = try Object.initFromXml(arena, doc, child_id);
objects.appendAssumeCapacity(object);
} else if (variant == .group and Layer.Type.name_map.has(child_name)) {
const layer = try initFromXml(arena, doc, child_id);
layers.appendAssumeCapacity(layer);
}
}
if (variant == .object) {
variant.object.items = objects.items;
}
if (variant == .group) {
variant.group.layers = layers.items;
}
return Layer{
@ -286,7 +330,3 @@ pub fn initFromXml(
.variant = variant,
};
}
pub fn isLayerNode(node: xml.TagParser.Node) bool {
return node.isTag("layer") or node.isTag("objectgroup") or node.isTag("imagelayer") or node.isTag("group");
}

View File

@ -3,6 +3,7 @@ const std = @import("std");
const xml = @import("./xml.zig");
const Position = @import("./position.zig");
const Color = @import("./color.zig");
const Property = @import("./property.zig");
const Object = @This();
@ -113,14 +114,15 @@ class: []const u8,
rotation: f32, // TODO: maybe this field should be moved to Shape struct
visible: bool,
shape: Shape,
properties: Property.List,
pub fn initFromXml(
arena: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
lexer: *xml.Lexer,
doc: xml.Document,
element_id: xml.Element.Id
) !Object {
var iter = xml.TagParser.init(lexer);
const attrs = try iter.begin("object");
const tag = try doc.getTagExpect(element_id, "object");
const attrs = tag.variant.tag.attributes;
const id = try attrs.getNumber(u32, "id") orelse return error.MissingId;
const name = try attrs.getDupe(arena, "name") orelse "";
@ -134,9 +136,14 @@ pub fn initFromXml(
var shape: ?Shape = null;
while (try iter.next()) |node| {
var iter = doc.iterChildren(element_id);
while (iter.next()) |child_id| {
const child = doc.getTag(child_id) orelse continue;
const child_name = child.variant.tag.name;
const child_attrs = child.variant.tag.attributes;
if (shape == null) {
if (node.isTag("point")) {
if (std.mem.eql(u8, child_name, "point")) {
shape = .{
.point = Shape.Point{
.x = x,
@ -144,7 +151,7 @@ pub fn initFromXml(
}
};
} else if (node.isTag("ellipse")) {
} else if (std.mem.eql(u8, child_name, "ellipse")) {
shape = .{
.ellipse = Shape.Ellipse{
.x = x,
@ -154,11 +161,11 @@ pub fn initFromXml(
}
};
} else if (node.isTag("text")) {
} else if (std.mem.eql(u8, child_name, "text")) {
const HorizontalShape = Shape.Text.HorizontalAlign;
const VerticalAlign = Shape.Text.VerticalAlign;
const text_attrs = node.tag.attributes;
const text_attrs = child_attrs;
const word_wrap = try text_attrs.getBool("wrap", "1", "0") orelse false;
const color = try text_attrs.getColor("color", true) orelse Color.black;
const horizontal_align = try text_attrs.getEnum(HorizontalShape, "halign", HorizontalShape.map) orelse .left;
@ -171,13 +178,13 @@ pub fn initFromXml(
const pixel_size = try text_attrs.getNumber(f32, "pixelsize") orelse 16;
const font_family = try text_attrs.getDupe(arena, "fontfamily") orelse "sans-serif";
_ = try lexer.nextExpectStartTag("text");
var content: []const u8 = "";
const content_value = try lexer.peek();
if (content_value != null and content_value.? == .text) {
content = try arena.dupe(u8, content_value.?.text);
const text_element = try doc.getTagExpect(child.tree.first_child, "text");
if (doc.get(text_element.tree.first_child)) |content_element| {
if (content_element.variant == .text) {
content = try arena.dupe(u8, content_element.variant.text);
}
}
try lexer.skipUntilMatchingEndTag("text");
shape = .{
.text = Shape.Text{
@ -203,14 +210,16 @@ pub fn initFromXml(
};
continue;
} else if (node.isTag("polygon")) {
const points_str = node.tag.attributes.get("points") orelse "";
} else if (std.mem.eql(u8, child_name, "polygon")) {
const points_str = child_attrs.get("points") orelse "";
var points: std.ArrayList(Position) = .empty;
try points.ensureTotalCapacityPrecise(arena, std.mem.countScalar(u8, points_str, ' ')+1);
var point_iter = std.mem.splitScalar(u8, points_str, ' ');
while (point_iter.next()) |point_str| {
const point = try Position.parseCommaDelimited(point_str);
try points.append(scratch.allocator(), point);
points.appendAssumeCapacity(point);
}
shape = .{
@ -223,7 +232,6 @@ pub fn initFromXml(
}
}
try iter.skip();
}
if (shape == null) {
@ -249,7 +257,7 @@ pub fn initFromXml(
}
}
try iter.finish("object");
const properties = try Property.List.initFromXmlContainer(arena, doc, element_id);
return Object{
.id = id,
@ -257,7 +265,8 @@ pub fn initFromXml(
.class = class,
.rotation = rotation,
.visible = visible,
.shape = shape orelse return error.UnknownShapeType
.shape = shape orelse return error.UnknownShapeType,
.properties = properties
};
}

View File

@ -33,32 +33,48 @@ pub const List = struct {
pub fn initFromXml(
arena: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
lexer: *xml.Lexer
doc: xml.Document,
element_id: xml.Element.Id
) !Property.List {
var iter = xml.TagParser.init(lexer);
_ = try iter.begin("properties");
_ = try doc.getTagExpect(element_id, "properties");
var temp_properties: std.ArrayList(Property) = .empty;
var properties: std.ArrayList(Property) = .empty;
try properties.ensureTotalCapacityPrecise(arena, doc.countChildren(element_id));
while (try iter.next()) |node| {
if (node.isTag("property")) {
const property = try Property.initFromXml(arena, lexer);
try temp_properties.append(scratch.allocator(), property);
continue;
var iter = doc.iterChildren(element_id);
while (iter.next()) |child_id| {
const child = doc.getTag(child_id) orelse continue;
const child_name = child.variant.tag.name;
if (std.mem.eql(u8, child_name, "property")) {
const property = try Property.initFromXml(arena, doc, child_id);
properties.appendAssumeCapacity(property);
}
try iter.skip();
}
try iter.finish("properties");
const properties = try arena.dupe(Property, temp_properties.items);
return List{
.items = properties
.items = properties.items
};
}
pub fn initFromXmlContainer(
arena: std.mem.Allocator,
doc: xml.Document,
element_id: xml.Element.Id
) !Property.List {
var iter = doc.iterChildren(element_id);
while (iter.next()) |child_id| {
const child = doc.getTag(child_id) orelse continue;
const child_name = child.variant.tag.name;
if (std.mem.eql(u8, child_name, "properties")) {
return try Property.List.initFromXml(arena, doc, child_id);
}
}
return .empty;
}
pub fn get(self: List, name: []const u8) ?Value {
for (self.items) |item| {
if (std.mem.eql(u8, item.name, name)) {
@ -70,8 +86,19 @@ pub const List = struct {
pub fn getString(self: List, name: []const u8) ?[]const u8 {
if (self.get(name)) |value| {
if (value == .string) {
return value.string;
}
}
return null;
}
pub fn getInt(self: List, name: []const u8) ?i32 {
if (self.get(name)) |value| {
if (value == .int) {
return value.int;
}
}
return null;
}
};
@ -83,9 +110,9 @@ pub fn init(name: []const u8, value: Value) Property {
};
}
pub fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Property {
var iter = xml.TagParser.init(lexer);
const attrs = try iter.begin("property");
pub fn initFromXml(arena: std.mem.Allocator, doc: xml.Document, element_id: xml.Element.Id) !Property {
const tag = try doc.getTagExpect(element_id, "property");
const attrs = tag.variant.tag.attributes;
const name = try attrs.getDupe(arena, "name") orelse return error.MissingName;
const prop_type_str = attrs.get("type") orelse "string";
@ -104,8 +131,6 @@ pub fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Property {
}
};
try iter.finish("property");
return Property{
.name = name,
.value = value

View File

@ -12,5 +12,5 @@ pub const Tileset = @import("./tileset.zig");
pub const Tilemap = @import("./tilemap.zig");
test {
_ = std.testing.refAllDeclsRecursive(@This());
_ = std.testing.refAllDecls(@This());
}

View File

@ -1,4 +1,5 @@
const std = @import("std");
const log = std.log;
const xml = @import("./xml.zig");
const Io = std.Io;
const assert = std.debug.assert;
@ -54,15 +55,13 @@ pub const TilesetReference = struct {
source: []const u8,
first_gid: u32,
pub fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !TilesetReference {
var iter = xml.TagParser.init(lexer);
const attrs = try iter.begin("tileset");
pub fn initFromXml(arena: std.mem.Allocator, doc: xml.Document, element_id: xml.Element.Id) !TilesetReference {
const tag = try doc.getTagExpect(element_id, "tileset");
const attrs = tag.variant.tag.attributes;
const source = try attrs.getDupe(arena, "source") orelse return error.MissingFirstGid;
const first_gid = try attrs.getNumber(u32, "firstgid") orelse return error.MissingFirstGid;
try iter.finish("tileset");
return TilesetReference{
.source = source,
.first_gid = first_gid
@ -106,40 +105,20 @@ next_object_id: u32,
tilesets: []TilesetReference,
layers: []Layer,
pub fn initFromBuffer(
gpa: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
xml_buffers: *xml.Lexer.Buffers,
buffer: []const u8
) !Tilemap {
var reader = Io.Reader.fixed(buffer);
return initFromReader(gpa, scratch, xml_buffers, &reader);
}
pub fn initFromReader(
gpa: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
xml_buffers: *xml.Lexer.Buffers,
reader: *Io.Reader,
) !Tilemap {
var lexer = xml.Lexer.init(reader, xml_buffers);
return initFromXml(gpa, scratch, &lexer);
}
// Map specification:
// https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#map
pub fn initFromXml(
gpa: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
lexer: *xml.Lexer,
doc: xml.Document,
element_id: xml.Element.Id
) !Tilemap {
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
errdefer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var iter = xml.TagParser.init(lexer);
const map_attrs = try iter.begin("map");
const tag = try doc.getTagExpect(element_id, "map");
const map_attrs = tag.variant.tag.attributes;
const version = try map_attrs.getDupe(arena, "version") orelse return error.MissingVersion;
const tiled_version = try map_attrs.getDupe(arena, "tiledversion");
@ -165,26 +144,43 @@ pub fn initFromXml(
// TODO: staggerindex
}
var layer_count: usize = 0;
var tileset_count: usize = 0;
{
var iter = doc.iterChildren(element_id);
while (iter.next()) |child_id| {
const child = doc.getTag(child_id) orelse continue;
const child_name = child.variant.tag.name;
if (std.mem.eql(u8, child_name, "tileset")) {
tileset_count += 1;
} else {
layer_count += 1;
}
}
}
var tileset_list: std.ArrayList(TilesetReference) = .empty;
try tileset_list.ensureTotalCapacityPrecise(arena, tileset_count);
var layer_list: std.ArrayList(Layer) = .empty;
try layer_list.ensureTotalCapacityPrecise(arena, layer_count);
while (try iter.next()) |node| {
if (node.isTag("tileset")) {
try tileset_list.append(scratch.allocator(), try TilesetReference.initFromXml(arena, lexer));
continue;
} else if (Layer.isLayerNode(node)) {
const layer = try Layer.initFromXml(
var iter = doc.iterChildren(element_id);
while (iter.next()) |child_id| {
const child = doc.getTag(child_id) orelse continue;
const child_name = child.variant.tag.name;
if (std.mem.eql(u8, child_name, "tileset")) {
tileset_list.appendAssumeCapacity(try TilesetReference.initFromXml(arena, doc, child_id));
} else {
layer_list.appendAssumeCapacity(try Layer.initFromXml(
arena,
scratch,
lexer,
);
try layer_list.append(scratch.allocator(), layer);
continue;
doc,
child_id
));
}
try iter.skip();
}
try iter.finish("map");
const tilesets = try arena.dupe(TilesetReference, tileset_list.items);
const layers = try arena.dupe(Layer, layer_list.items);
@ -223,7 +219,7 @@ fn getTilesetByGid(self: *const Tilemap, gid: u32) ?TilesetReference {
return result;
}
pub fn getTile(self: *const Tilemap, layer: *const Layer, tilesets: Tileset.List, x: usize, y: usize) ?Tile {
pub fn getTile(self: *const Tilemap, layer: *const Layer, tilesets: Tileset.List, x: i32, y: i32) ?Tile {
assert(layer.variant == .tile);
const tile_variant = layer.variant.tile;
@ -248,10 +244,10 @@ pub fn getLayer(self: *const Tilemap, name: []const u8) ?*const Layer {
}
pub const Bounds = struct {
min_x: usize,
min_y: usize,
max_x: usize,
max_y: usize,
min_x: i32,
min_y: i32,
max_x: i32,
max_y: i32,
const zero = Bounds{
.min_x = 0,
@ -260,12 +256,16 @@ pub const Bounds = struct {
.max_y = 0,
};
pub fn width(self: Bounds) usize {
return self.max_x - self.min_x + 1;
pub fn width(self: Bounds) u32 {
return @intCast(self.max_x - self.min_x + 1);
}
pub fn height(self: Bounds) usize {
return self.max_y - self.min_y + 1;
pub fn height(self: Bounds) u32 {
return @intCast(self.max_y - self.min_y + 1);
}
pub fn isInside(self: Bounds, x: i32, y: i32) bool {
return (self.min_x <= x and x <= self.max_x) and (self.min_y <= y and y <= self.max_y);
}
};
@ -277,26 +277,22 @@ pub fn getTileBounds(self: *const Tilemap) Bounds {
continue;
}
for (0..self.height) |y| {
for (0..self.width) |x| {
const gid = layer.variant.tile.get(x, y).?;
if (gid != 0) {
for (layer.variant.tile.chunks) |chunk| {
const chunk_bounds = Bounds{
.min_x = chunk.x,
.min_y = chunk.y,
.max_x = chunk.x + @as(i32, @intCast(chunk.width)),
.max_y = chunk.y + @as(i32, @intCast(chunk.height))
};
if (result) |bounds| {
result = Bounds{
.min_x = @min(x, bounds.min_x),
.min_y = @min(y, bounds.min_y),
.max_x = @max(x, bounds.max_x),
.max_y = @max(y, bounds.max_y)
.min_x = @min(chunk_bounds.min_x, bounds.min_x),
.min_y = @min(chunk_bounds.min_y, bounds.min_y),
.max_x = @max(chunk_bounds.max_x, bounds.max_x),
.max_y = @max(chunk_bounds.max_y, bounds.max_y)
};
} else {
result = Bounds{
.min_x = x,
.min_y = y,
.max_x = x,
.max_y = y
};
}
}
result = chunk_bounds;
}
}
}
@ -304,6 +300,59 @@ pub fn getTileBounds(self: *const Tilemap) Bounds {
return result orelse Bounds.zero;
}
fn isRowEmpty(tile_layer: Layer.TileVariant, x: i32, y: i32, length: u32) bool {
for (0..length) |i| {
const gid = tile_layer.get(x + @as(i32, @intCast(i)), y) orelse continue;
if (gid != 0) {
return false;
}
}
return true;
}
fn isColumnEmpty(tile_layer: Layer.TileVariant, x: i32, y: i32, length: u32) bool {
for (0..length) |i| {
const gid = tile_layer.get(x, y + @as(i32, @intCast(i))) orelse continue;
if (gid != 0) {
return false;
}
}
return true;
}
pub fn trimBoundsByEmptyTiles(self: *const Tilemap, bounds: Bounds) Bounds {
var result: Bounds = bounds;
for (self.layers) |*layer| {
if (layer.variant != .tile) {
continue;
}
const tile_layer = layer.variant.tile;
// Trim left
while (result.min_x < result.max_x and isColumnEmpty(tile_layer, result.min_x, result.min_y, result.height())) {
result.min_x += 1;
}
// Trim right
while (result.min_x < result.max_x and isColumnEmpty(tile_layer, result.max_x, result.min_y, result.height())) {
result.max_x -= 1;
}
// Trim top
while (result.min_y < result.max_y and isRowEmpty(tile_layer, result.min_x, result.min_y, result.width())) {
result.min_y += 1;
}
// Trim bottom
while (result.min_y < result.max_y and isRowEmpty(tile_layer, result.min_x, result.max_y, result.width())) {
result.max_y -= 1;
}
}
return result;
}
pub fn deinit(self: *const Tilemap) void {
self.arena.deinit();
}

View File

@ -14,16 +14,14 @@ pub const Image = struct {
width: u32,
height: u32,
pub fn intFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Image {
var iter = xml.TagParser.init(lexer);
const attrs = try iter.begin("image");
pub fn intFromXml(arena: std.mem.Allocator, doc: xml.Document, element_id: xml.Element.Id) !Image {
const tag = try doc.getTagExpect(element_id, "image");
const attrs = tag.variant.tag.attributes;
const source = try attrs.getDupe(arena, "width") orelse return error.MissingSource;
const width = try attrs.getNumber(u32, "width") orelse return error.MissingWidth;
const height = try attrs.getNumber(u32, "height") orelse return error.MissingHeight;
try iter.finish("image");
return Image{
.source = source,
.width = width,
@ -38,26 +36,14 @@ pub const Tile = struct {
pub fn initFromXml(
arena: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
lexer: *xml.Lexer,
doc: xml.Document,
element_id: xml.Element.Id
) !Tile {
var iter = xml.TagParser.init(lexer);
const tile_attrs = try iter.begin("tile");
const tag = try doc.getTagExpect(element_id, "tile");
const tile_attrs = tag.variant.tag.attributes;
const id = try tile_attrs.getNumber(u32, "id") orelse return error.MissingId;
var properties: Property.List = .empty;
while (try iter.next()) |node| {
if (node.isTag("properties")) {
properties = try Property.List.initFromXml(arena, scratch, lexer);
continue;
}
try iter.skip();
}
try iter.finish("tile");
const properties = try Property.List.initFromXmlContainer(arena, doc, element_id);
return Tile{
.id = id,
@ -124,36 +110,16 @@ image: Image,
tiles: []Tile,
pub fn initFromBuffer(
gpa: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
xml_buffers: *xml.Lexer.Buffers,
buffer: []const u8
) !Tileset {
var reader = Io.Reader.fixed(buffer);
return initFromReader(gpa, scratch, xml_buffers, &reader);
}
pub fn initFromReader(
gpa: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
xml_buffers: *xml.Lexer.Buffers,
reader: *Io.Reader,
) !Tileset {
var lexer = xml.Lexer.init(reader, xml_buffers);
return initFromXml(gpa, scratch, &lexer);
}
pub fn initFromXml(
gpa: std.mem.Allocator,
scratch: *std.heap.ArenaAllocator,
lexer: *xml.Lexer
doc: xml.Document,
element_id: xml.Element.Id
) !Tileset {
var arena_state = std.heap.ArenaAllocator.init(gpa);
const arena = arena_state.allocator();
var iter = xml.TagParser.init(lexer);
const tileset_attrs = try iter.begin("tileset");
const tag = try doc.getTagExpect(element_id, "tileset");
const tileset_attrs = tag.variant.tag.attributes;
const version = try tileset_attrs.getDupe(arena, "version") orelse return error.MissingTilesetTag;
const tiled_version = try tileset_attrs.getDupe(arena, "tiledversion") orelse return error.MissingTilesetTag;
@ -165,20 +131,20 @@ pub fn initFromXml(
var image: ?Image = null;
var tiles_list: std.ArrayList(Tile) = .empty;
try tiles_list.ensureTotalCapacityPrecise(arena, doc.countChildren(element_id));
while (try iter.next()) |node| {
if (node.isTag("image")) {
image = try Image.intFromXml(arena, lexer);
continue;
} else if (node.isTag("tile")) {
const tile = try Tile.initFromXml(arena, scratch, lexer);
try tiles_list.append(scratch.allocator(), tile);
continue;
}
var iter = doc.iterChildren(element_id);
while (iter.next()) |child_id| {
const child = doc.getTag(child_id) orelse continue;
const child_name = child.variant.tag.name;
try iter.skip();
if (std.mem.eql(u8, child_name, "image")) {
image = try Image.intFromXml(arena, doc, child_id);
} else if (std.mem.eql(u8, child_name, "tile")) {
const tile = try Tile.initFromXml(arena, doc, child_id);
tiles_list.appendAssumeCapacity(tile);
}
}
try iter.finish("tileset");
const tiles = try arena.dupe(Tile, tiles_list.items);

View File

@ -1,5 +1,6 @@
const std = @import("std");
const Io = std.Io;
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const Color = @import("./color.zig");
@ -105,102 +106,160 @@ pub const Attribute = struct {
}
};
pub const Tag = struct {
pub const Element = struct {
variant: union(enum) {
tag: struct {
name: []const u8,
attributes: Attribute.List
attributes: Attribute.List,
},
text: []const u8,
},
// TODO: Maybe an element should contain a "inner contents" field?
// With which you could index into the text content.
// But, need to figure out how it would interact with comments.
//
// Maybe comment removal should be a separate pass?
tree: struct {
first_child: Id = .nil,
last_child: Id = .nil,
next_sibling: Id = .nil,
} = .{},
pub const Id = enum(i32) {
_,
pub const nil: Id = @enumFromInt(-1);
pub const root: Id = @enumFromInt(0);
};
};
pub const Lexer = struct {
pub const Buffers = struct {
scratch: std.heap.ArenaAllocator,
text: std.ArrayList(u8),
pub const Document = struct {
elements: []Element,
pub fn init(allocator: std.mem.Allocator) Buffers {
return Buffers{
.scratch = std.heap.ArenaAllocator.init(allocator),
.text = .empty
pub fn get(self: Document, id: Element.Id) ?Element {
if (id == Element.Id.nil) {
return null;
}
const index = @intFromEnum(id);
assert(index >= 0);
return self.elements[@intCast(index)];
}
pub fn getTag(self: Document, id: Element.Id) ?Element {
const element = self.get(id) orelse return null;
if (element.variant == .tag) {
return element;
}
return null;
}
pub fn getTagExpect(self: Document, id: Element.Id, name: []const u8) !Element {
const tag = self.getTag(id) orelse return error.TagNotFound;
const tag_name = tag.variant.tag.name;
if (!std.mem.eql(u8, tag_name, name)) {
return error.UnexpectedTagName;
}
return tag;
}
pub fn iterChildren(self: Document, id: Element.Id) SiblingIterator {
var first_child: Element.Id = .nil;
if (self.get(id)) |tag| {
first_child = tag.tree.first_child;
}
return SiblingIterator{
.doc = self,
.current = first_child
};
}
pub fn clear(self: *Buffers) void {
self.text.clearRetainingCapacity();
_ = self.scratch.reset(.retain_capacity);
pub fn countChildren(self: Document, id: Element.Id) usize {
var count: usize = 0;
var iter = self.iterChildren(id);
while (iter.next()) |_| {
count += 1;
}
return count;
}
pub fn deinit(self: *Buffers) void {
const allocator = self.scratch.child_allocator;
self.scratch.deinit();
self.text.deinit(allocator);
const SiblingIterator = struct {
doc: Document,
current: Element.Id,
pub fn next(self: *SiblingIterator) ?Element.Id {
const element = self.doc.get(self.current) orelse return null;
const current = self.current;
self.current = element.tree.next_sibling;
return current;
}
};
};
const Lexer = struct {
pub const Token = union(enum) {
start_tag: Tag,
start_tag_begin: []const u8,
attribute: Attribute,
start_tag_end: []const u8,
end_tag: []const u8,
text: []const u8,
// TODO: Add comment type?
pub fn isStartTag(self: Token, name: []const u8) bool {
if (self == .start_tag) {
return std.mem.eql(u8, self.start_tag.name, name);
pub fn format(self: Token, writer: *Io.Writer) Io.Writer.Error!void {
switch(self) {
.start_tag_begin => |name| try writer.print("Token{{ .start_tag_begin, {s} }}", .{name}),
.attribute => |attr| try writer.print("Token{{ .attribute, {s}='{s}' }}", .{attr.name, attr.value}),
.start_tag_end => try writer.print("Token{{ .start_tag_end }}", .{}),
.end_tag => |name| try writer.print("Token{{ .end_tag, {s} }}", .{name}),
.text => |text| try writer.print("Token{{ .text, {s} }}", .{text}),
}
return false;
}
pub fn isEndTag(self: Token, name: []const u8) bool {
if (self == .end_tag) {
return std.mem.eql(u8, self.end_tag, name);
}
return false;
}
};
pub const StepResult = struct {
token: ?Token,
self_closing: bool = false,
};
text: []const u8,
cursor: usize = 0,
inside_start_tag: ?[]const u8 = null,
queued_end_tag: ?[]const u8 = null,
pub const TestingContext = struct {
io_reader: Io.Reader,
buffers: Buffers,
lexer: Lexer,
pub fn init(self: *TestingContext, allocator: std.mem.Allocator, body: []const u8) void {
self.* = TestingContext{
.lexer = undefined,
.io_reader = Io.Reader.fixed(body),
.buffers = Buffers.init(allocator)
};
self.lexer = Lexer.init(&self.io_reader, &self.buffers);
}
pub fn deinit(self: *TestingContext) void {
self.buffers.deinit();
}
};
io_reader: *Io.Reader,
buffers: *Buffers,
peeked_value: ?Token,
cursor: usize,
queued_end_tag: ?[]const u8,
pub fn init(reader: *Io.Reader, buffers: *Buffers) Lexer {
buffers.clear();
return Lexer{
.io_reader = reader,
.buffers = buffers,
.cursor = 0,
.queued_end_tag = null,
.peeked_value = null
pub fn next(self: *Lexer) !?Token {
if (self.queued_end_tag) |name| {
self.queued_end_tag = null;
return Token{
.end_tag = name
};
}
fn step(self: *Lexer) !StepResult {
_ = self.buffers.scratch.reset(.retain_capacity);
while (self.cursor < self.text.len) {
if (self.inside_start_tag) |tag_name| {
if (try self.parseAttribute()) |attribute| {
return Token{
.attribute = attribute
};
} else {
try self.skipWhiteSpace();
if (try self.peekByte() == '<') {
if (std.mem.eql(u8, try self.peekBytes(1), ">")) {
self.tossBytes(1);
} else if (std.mem.eql(u8, try self.peekBytes(2), "/>")) {
self.tossBytes(2);
self.queued_end_tag = tag_name;
} else {
return error.UnfinishedStartTag;
}
self.inside_start_tag = null;
return Token{
.start_tag_end = tag_name
};
}
} else if (try self.peekByte() == '<') {
self.tossByte();
if (try self.peekByte() == '/') {
@ -214,8 +273,7 @@ pub const Lexer = struct {
return error.InvalidEndTag;
}
const token = Token{ .end_tag = name };
return .{ .token = token };
return Token{ .end_tag = name };
} else if (try self.peekByte() == '?') {
// Prolog tag
@ -224,9 +282,14 @@ pub const Lexer = struct {
return error.InvalidPrologTag;
}
const attributes = try self.parseAttributes();
var attributes_buf: [8]Attribute = undefined;
const attributes_len = try self.parseAttributesBounded(&attributes_buf);
try self.skipWhiteSpace();
const attributes = Attribute.List{
.items = attributes_buf[0..attributes_len]
};
if (!std.mem.eql(u8, try self.takeBytes(2), "?>")) {
return error.MissingPrologEnd;
}
@ -240,104 +303,31 @@ pub const Lexer = struct {
return error.InvalidPrologEncoding;
}
return .{ .token = null };
} else {
// Start tag
const name = try self.parseName();
const attributes = try self.parseAttributes();
try self.skipWhiteSpace();
self.inside_start_tag = name;
const token = Token{
.start_tag = .{
.name = name,
.attributes = attributes
}
};
var self_closing = false;
if (std.mem.eql(u8, try self.peekBytes(1), ">")) {
self.tossBytes(1);
} else if (std.mem.eql(u8, try self.peekBytes(2), "/>")) {
self.tossBytes(2);
self_closing = true;
} else {
return error.UnfinishedStartTag;
}
return .{
.token = token,
.self_closing = self_closing
return Token{
.start_tag_begin = name
};
}
} else {
try self.skipWhiteSpace();
if (!self.hasBytes(1)) {
continue;
}
const text_start = self.cursor;
while (try self.peekByte() != '<') {
self.tossByte();
}
var text: []const u8 = self.buffers.text.items[text_start..self.cursor];
var text: []const u8 = self.text[text_start..self.cursor];
text = std.mem.trimEnd(u8, text, &std.ascii.whitespace);
var token: ?Token = null;
if (text.len > 0) {
token = Token{ .text = text };
return Token{ .text = text };
}
return .{ .token = token };
}
}
pub fn next(self: *Lexer) !?Token {
if (self.peeked_value) |value| {
self.peeked_value = null;
return value;
}
if (self.queued_end_tag) |name| {
self.queued_end_tag = null;
return Token{
.end_tag = name
};
}
while (true) {
if (self.buffers.text.items.len == 0) {
self.readIntoTextBuffer() catch |e| switch (e) {
error.EndOfStream => break,
else => return e
};
}
const saved_cursor = self.cursor;
const result = self.step() catch |e| switch(e) {
error.EndOfTextBuffer => {
self.cursor = saved_cursor;
const unused_capacity = self.buffers.text.capacity - self.buffers.text.items.len;
if (unused_capacity == 0 and self.cursor > 0) {
self.rebaseBuffer();
} else {
self.readIntoTextBuffer() catch |read_err| switch (read_err) {
error.EndOfStream => break,
else => return read_err
};
}
continue;
},
else => return e
};
if (result.token) |token| {
if (token == .start_tag and result.self_closing) {
self.queued_end_tag = token.start_tag.name;
}
return token;
}
}
@ -381,45 +371,6 @@ pub const Lexer = struct {
}
}
pub fn peek(self: *Lexer) !?Token {
if (try self.next()) |value| {
self.peeked_value = value;
return value;
}
return null;
}
fn readIntoTextBuffer(self: *Lexer) !void {
const gpa = self.buffers.scratch.child_allocator;
const text = &self.buffers.text;
try text.ensureUnusedCapacity(gpa, 1);
var writer = Io.Writer.fixed(text.allocatedSlice());
writer.end = text.items.len;
_ = self.io_reader.stream(&writer, .limited(text.capacity - text.items.len)) catch |e| switch (e) {
error.WriteFailed => unreachable,
else => |ee| return ee
};
text.items.len = writer.end;
}
fn rebaseBuffer(self: *Lexer) void {
if (self.cursor == 0) {
return;
}
const text = &self.buffers.text;
@memmove(
text.items[0..(text.items.len - self.cursor)],
text.items[self.cursor..]
);
text.items.len -= self.cursor;
self.cursor = 0;
}
fn isNameStartChar(c: u8) bool {
return c == ':' or c == '_' or std.ascii.isAlphabetic(c);
}
@ -429,16 +380,14 @@ pub const Lexer = struct {
}
fn hasBytes(self: *Lexer, n: usize) bool {
const text = self.buffers.text.items;
return self.cursor + n <= text.len;
return self.cursor + n <= self.text.len;
}
fn peekBytes(self: *Lexer, n: usize) ![]const u8 {
if (self.hasBytes(n)) {
const text = self.buffers.text.items;
return text[self.cursor..][0..n];
return self.text[self.cursor..][0..n];
}
return error.EndOfTextBuffer;
return error.UnexpectedEOF;
}
fn tossBytes(self: *Lexer, n: usize) void {
@ -475,11 +424,11 @@ pub const Lexer = struct {
}
}
return self.buffers.text.items[name_start..self.cursor];
return self.text[name_start..self.cursor];
}
fn skipWhiteSpace(self: *Lexer) !void {
while (std.ascii.isWhitespace(try self.peekByte())) {
while (self.cursor < self.text.len and std.ascii.isWhitespace(try self.peekByte())) {
self.tossByte();
}
}
@ -504,18 +453,14 @@ pub const Lexer = struct {
value_len += 1;
}
return self.buffers.text.items[value_start..][0..value_len];
return self.text[value_start..][0..value_len];
}
fn parseAttributes(self: *Lexer) !Attribute.List {
const arena = self.buffers.scratch.allocator();
var attributes: std.ArrayList(Attribute) = .empty;
while (true) {
fn parseAttribute(self: *Lexer) !?Attribute {
try self.skipWhiteSpace();
const name = try self.parseName();
if (name.len == 0) {
break;
return null;
}
try self.skipWhiteSpace();
@ -525,163 +470,287 @@ pub const Lexer = struct {
try self.skipWhiteSpace();
const value = try self.parseAttributeValue();
return Attribute{
.name = name,
.value = value
};
}
fn parseAttributesBounded(self: *Lexer, buffer: []Attribute) !usize {
var attributes: std.ArrayList(Attribute) = .initBuffer(buffer);
while (try self.parseAttribute()) |attr| {
const list = Attribute.List{ .items = attributes.items };
if (list.get(name) != null) {
if (list.get(attr.name) != null) {
return error.DuplicateAttribute;
}
try attributes.append(arena, Attribute{
.name = name,
.value = value
});
try attributes.appendBounded(attr);
}
return Attribute.List{
.items = attributes.items
};
return attributes.items.len;
}
fn expectStartTagBegin(lexer: *Lexer, expected_name: []const u8) !void {
const token = (try lexer.next()).?;
try std.testing.expect(token == .start_tag_begin);
try std.testing.expectEqualStrings(expected_name, token.start_tag_begin);
}
fn expectStartTagEnd(lexer: *Lexer) !void {
const token = (try lexer.next()).?;
try std.testing.expect(token == .start_tag_end);
}
fn expectEndTag(lexer: *Lexer, expected_name: []const u8) !void {
const token = (try lexer.next()).?;
try std.testing.expect(token == .end_tag);
try std.testing.expectEqualStrings(expected_name, token.end_tag);
}
fn expectDone(lexer: *Lexer) !void {
try std.testing.expect(try lexer.next() == null);
}
fn expectText(lexer: *Lexer, expected_text: []const u8) !void {
const token = (try lexer.next()).?;
try std.testing.expect(token == .text);
try std.testing.expectEqualStrings(expected_text, token.text);
}
fn expectAttribute(lexer: *Lexer, expected: Attribute) !void {
const token = (try lexer.next()).?;
try std.testing.expect(token == .attribute);
try std.testing.expectEqualStrings(expected.name, token.attribute.name);
try std.testing.expectEqualStrings(expected.value, token.attribute.value);
}
test "self closing tag" {
const allocator = std.testing.allocator;
var ctx: TestingContext = undefined;
ctx.init(allocator,
const text =
\\ <hello />
);
defer ctx.deinit();
;
try std.testing.expect((try ctx.lexer.next()).?.isStartTag("hello"));
try std.testing.expect((try ctx.lexer.next()).?.isEndTag("hello"));
try std.testing.expect((try ctx.lexer.next()) == null);
var lexer: Lexer = .{ .text = text };
try expectStartTagBegin(&lexer, "hello");
try expectStartTagEnd(&lexer);
try expectEndTag(&lexer, "hello");
try expectDone(&lexer);
}
test "tag" {
const allocator = std.testing.allocator;
var ctx: TestingContext = undefined;
ctx.init(allocator,
const text =
\\ <hello></hello>
);
defer ctx.deinit();
;
try std.testing.expect((try ctx.lexer.next()).?.isStartTag("hello"));
try std.testing.expect((try ctx.lexer.next()).?.isEndTag("hello"));
try std.testing.expect((try ctx.lexer.next()) == null);
var lexer: Lexer = .{ .text = text };
try expectStartTagBegin(&lexer, "hello");
try expectStartTagEnd(&lexer);
try expectEndTag(&lexer, "hello");
try expectDone(&lexer);
}
test "tag with prolog" {
const allocator = std.testing.allocator;
var ctx: TestingContext = undefined;
ctx.init(allocator,
const text =
\\ <?xml version="1.0" encoding="UTF-8"?>
\\ <hello></hello>
);
defer ctx.deinit();
;
try std.testing.expect((try ctx.lexer.next()).?.isStartTag("hello"));
try std.testing.expect((try ctx.lexer.next()).?.isEndTag("hello"));
try std.testing.expect((try ctx.lexer.next()) == null);
var lexer: Lexer = .{ .text = text };
try expectStartTagBegin(&lexer, "hello");
try expectStartTagEnd(&lexer);
try expectEndTag(&lexer, "hello");
try expectDone(&lexer);
}
test "text content" {
const allocator = std.testing.allocator;
var ctx: TestingContext = undefined;
ctx.init(allocator,
const text =
\\ <hello> Hello World </hello>
);
defer ctx.deinit();
;
try std.testing.expect((try ctx.lexer.next()).?.isStartTag("hello"));
try std.testing.expectEqualStrings("Hello World", (try ctx.lexer.next()).?.text);
try std.testing.expect((try ctx.lexer.next()).?.isEndTag("hello"));
try std.testing.expect((try ctx.lexer.next()) == null);
var lexer: Lexer = .{ .text = text };
try expectStartTagBegin(&lexer, "hello");
try expectStartTagEnd(&lexer);
try expectText(&lexer, "Hello World");
try expectEndTag(&lexer, "hello");
try expectDone(&lexer);
}
test "attributes" {
const allocator = std.testing.allocator;
var ctx: TestingContext = undefined;
ctx.init(allocator,
const text =
\\ <hello a='1' b='2'/>
;
var lexer: Lexer = .{ .text = text };
try expectStartTagBegin(&lexer, "hello");
try expectAttribute(&lexer, .{ .name = "a", .value = "1" });
try expectAttribute(&lexer, .{ .name = "b", .value = "2" });
try expectStartTagEnd(&lexer);
try expectEndTag(&lexer, "hello");
try expectDone(&lexer);
}
};
pub const Parser = struct {
elements: []Element,
attributes: []Attribute,
stack: []Element.Id,
const InitOptions = struct {
max_elements: u32 = 4096,
max_attributes: u32 = 4096,
max_depth: u32 = 256,
};
pub fn initCapacity(gpa: Allocator, opts: InitOptions) !Parser {
const tags = try gpa.alloc(Element, opts.max_elements);
errdefer gpa.free(tags);
const attributes = try gpa.alloc(Attribute, opts.max_attributes);
errdefer gpa.free(attributes);
const stack = try gpa.alloc(Element.Id, opts.max_depth);
errdefer gpa.free(stack);
return Parser{
.elements = tags,
.attributes = attributes,
.stack = stack,
};
}
pub fn deinit(self: Parser, gpa: Allocator) void {
gpa.free(self.elements);
gpa.free(self.attributes);
gpa.free(self.stack);
}
const ParserContext = struct {
elements: std.ArrayList(Element),
stack: std.ArrayList(Element.Id),
fn getElement(self: *ParserContext, id: Element.Id) *Element {
return &self.elements.items[@intCast(@intFromEnum(id))];
}
pub fn pushElement(self: *ParserContext) !*Element {
const element_index = self.elements.items.len;
const element_id: Element.Id = @enumFromInt(element_index);
const element = self.elements.addOneBounded() catch return error.TooManyElements;
element.tree = .{};
if (self.stack.getLastOrNull()) |parent_id| {
const parent = self.getElement(parent_id);
if (parent.tree.last_child != Element.Id.nil) {
const last_child = &self.elements.items[@intCast(@intFromEnum(parent.tree.last_child))];
last_child.tree.next_sibling = element_id;
} else {
parent.tree.first_child = element_id;
}
parent.tree.last_child = element_id;
}
self.stack.appendBounded(element_id) catch return error.StackTooDeep;
return element;
}
pub fn popElement(self: *ParserContext) !void {
if (self.stack.items.len == 0) {
return error.UnbalancedBeginAndEndTags;
}
_ = self.stack.pop();
}
};
pub fn parse(self: Parser, text: []const u8) !Document {
var ctx = ParserContext{
.elements = .initBuffer(self.elements),
.stack = .initBuffer(self.stack)
};
var attributes_used: usize = 0;
var attributes: std.ArrayList(Attribute) = .initBuffer(self.attributes[attributes_used..]);
var lexer = Lexer{ .text = text };
while (try lexer.next()) |token| {
switch (token) {
.start_tag_begin => |name| {
_ = name; // autofix
},
.attribute => |attribute| {
attributes.appendBounded(attribute) catch return error.TooManyAttributes;
},
.start_tag_end => |name| {
const element = try ctx.pushElement();
element.variant = .{
.tag = .{
.name = name,
.attributes = .{ .items = attributes.items }
}
};
attributes_used += attributes.items.len;
attributes = .initBuffer(self.attributes[attributes_used..]);
},
.end_tag => {
try ctx.popElement();
},
.text => |element_text| {
const element = try ctx.pushElement();
element.variant = .{
.text = element_text
};
try ctx.popElement();
},
}
}
if (ctx.stack.items.len > 0) {
return error.UnbalancedBeginAndEndTags;
}
return Document{
.elements = ctx.elements.items
};
}
test {
const gpa = std.testing.allocator;
var parser = try Parser.initCapacity(gpa, .{});
defer parser.deinit(gpa);
const doc = try parser.parse(
\\ <text>Hello, World!</text>
);
defer ctx.deinit();
const root = try doc.getTagExpect(.root, "text");
const first_child = doc.get(root.tree.first_child).?;
try std.testing.expectEqualStrings("Hello, World!", first_child.variant.text);
}
const token = try ctx.lexer.next();
const attrs = token.?.start_tag.attributes;
try std.testing.expectEqualStrings("1", attrs.get("a").?);
try std.testing.expectEqualStrings("2", attrs.get("b").?);
test {
const gpa = std.testing.allocator;
var parser = try Parser.initCapacity(gpa, .{});
defer parser.deinit(gpa);
const doc = try parser.parse(
\\ <container>
\\ <box>1</box>
\\ <box>2</box>
\\ <box>3</box>
\\ </container>
);
var iter = doc.iterChildren(.root);
var count: usize = 0;
while (iter.next()) |_| {
count += 1;
}
try std.testing.expectEqual(3, count);
}
};
// TODO: The API for this is easy to misuse.
// Design a better API for using Reader
// As a compromise `assert` was used to guard against some of the ways this can be misused
pub const TagParser = struct {
lexer: *Lexer,
begin_called: bool = false,
finish_called: bool = false,
pub const Node = union(enum) {
tag: Tag,
text: []const u8,
pub fn isTag(self: Node, name: []const u8) bool {
if (self == .tag) {
return std.mem.eql(u8, self.tag.name, name);
}
return false;
}
};
pub fn init(lexer: *Lexer) TagParser {
return TagParser{
.lexer = lexer,
};
}
pub fn begin(self: *TagParser, name: []const u8) !Attribute.List {
assert(!self.begin_called);
self.begin_called = true;
return try self.lexer.nextExpectStartTag(name);
}
pub fn finish(self: *TagParser, name: []const u8) !void {
assert(self.begin_called);
assert(!self.finish_called);
self.finish_called = true;
try self.lexer.skipUntilMatchingEndTag(name);
}
pub fn next(self: *TagParser) !?Node {
assert(self.begin_called);
assert(!self.finish_called);
const value = try self.lexer.peek() orelse return error.MissingEndTag;
if (value == .end_tag) {
return null;
}
return switch (value) {
.text => |text| Node{ .text = text },
.start_tag => |start_tag| Node{ .tag = start_tag },
.end_tag => unreachable,
};
}
pub fn skip(self: *TagParser) !void {
assert(self.begin_called);
assert(!self.finish_called);
const value = try self.lexer.next() orelse return error.MissingNode;
if (value == .end_tag) {
return error.UnexpectedEndTag;
} else if (value == .start_tag) {
// TODO: Make this configurable
var name_buffer: [64]u8 = undefined;
var name: std.ArrayList(u8) = .initBuffer(&name_buffer);
try name.appendSliceBounded(value.start_tag.name);
try self.lexer.skipUntilMatchingEndTag(name.items);
}
}
};
test {
_ = Lexer;
_ = Parser;
}

View File

@ -7,7 +7,7 @@ const STBImage = @import("stb_image");
const STBTrueType = @import("stb_truetype");
const Tiled = @import("tiled");
const Platform = @import("platform/root.zig");
const Platform = @import("platform");
const Gfx = Platform.Gfx;
const Vec2 = Platform.Vec2;
const Audio = Platform.Audio;
@ -47,11 +47,11 @@ const PlayerSprites = struct {
const Tile = struct {
ground_sprite: ?Gfx.Sprite.Id,
entity: Entity.Id,
entities_by_layer: std.EnumArray(Entity.Layer, Entity.Id),
const empty = Tile{
.ground_sprite = null,
.entity = .nil
.entities_by_layer = .initFill(.nil)
};
};
@ -71,6 +71,17 @@ const Tilemap = struct {
};
}
pub fn clone(self: Tilemap, gpa: Allocator) !Tilemap {
const tiles = try gpa.dupe(Tile, self.tiles);
errdefer gpa.free(tiles);
return Tilemap{
.tiles = tiles,
.width = self.width,
.height = self.height,
};
}
pub fn deinit(self: *Tilemap, gpa: Allocator) void {
gpa.free(self.tiles);
}
@ -84,6 +95,7 @@ const Tilemap = struct {
const Entity = struct {
type: Type,
layer: Layer,
pos_x: i32 = -1,
pos_y: i32 = -1,
@ -96,9 +108,15 @@ const Entity = struct {
const Type = enum {
player,
box,
goal_marker,
wall
};
const Layer = enum {
ground,
surface,
};
const SlotMap = Platform.SlotMapType(u16, u8, Entity);
const Id = SlotMap.Id;
};
@ -107,24 +125,45 @@ const World = struct {
tilemap: Tilemap,
entities: Entity.SlotMap,
fn spawnPlayer(self: *World) !Entity.Id {
return try self.entities.insert(Entity{
.type = .player,
});
pub fn clone(self: World, gpa: Allocator) !World {
var tilemap = try self.tilemap.clone(gpa);
errdefer tilemap.deinit(gpa);
var entity_slots = try self.entities.slots.clone(gpa);
errdefer entity_slots.deinit(gpa);
var entities = self.entities;
entities.slots = entity_slots;
return World{
.tilemap = tilemap,
.entities = entities
};
}
fn spawnBox(self: *World, sprite: Gfx.Sprite.Id) !Entity.Id {
return try self.entities.insert(Entity{
.type = .box,
.sprite = sprite
});
pub fn deinit(self: *World, gpa: Allocator) void {
self.tilemap.deinit(gpa);
self.entities.deinit(gpa);
}
fn spawnWall(self: *World, sprite: Gfx.Sprite.Id) !Entity.Id {
return try self.entities.insert(Entity{
.type = .wall,
.sprite = sprite
});
fn spawnEntity(self: *World, opts: Entity) Entity.Id {
var e = opts;
e.pos_x = -1;
e.pos_y = -1;
const entity_id = self.entities.insert(e) catch |err| {
log.err("Failed to spawn entity: {}", .{err});
return .nil;
};
if (opts.pos_x != -1 or opts.pos_y != -1) {
assert(opts.pos_x != -1 and opts.pos_y != -1);
if (!self.setEntityPosition(entity_id, opts.pos_x, opts.pos_y)) {
log.err("Entity position: {}, {}", .{opts.pos_x, opts.pos_y});
@panic("Failed to set initial entity position");
}
}
return entity_id;
}
fn getTile(self: *World, x: i32, y: i32) ?*Tile {
@ -136,18 +175,18 @@ const World = struct {
return null;
}
fn moveEntity(self: *World, id: Entity.Id, x: i32, y: i32) bool {
fn setEntityPosition(self: *World, id: Entity.Id, x: i32, y: i32) bool {
const e = self.entities.get(id) orelse return false;
const destination_tile = self.getTile(x, y) orelse return false;
if (self.entities.exists(destination_tile.entity)) {
// Cna't overwrite entity
if (self.entities.exists(destination_tile.entities_by_layer.get(e.layer))) {
// Cant't overwrite entity
return false;
}
destination_tile.entity = id;
destination_tile.entities_by_layer.set(e.layer, id);
if (self.getTile(e.pos_x, e.pos_y)) |tile| {
tile.entity = .nil;
tile.entities_by_layer.set(e.layer, .nil);
}
e.pos_x = x;
@ -156,20 +195,37 @@ const World = struct {
return true;
}
fn movePlayer(self: *World, id: Entity.Id, dir_x: i32, dir_y: i32) bool {
fn moveEntity(self: *World, id: Entity.Id, dx: i32, dy: i32) bool {
const e = self.entities.get(id) orelse return false;
const next_x = e.pos_x+dir_x;
const next_y = e.pos_y+dir_y;
if (!self.moveEntity(id, next_x, next_y)) {
const next_x = e.pos_x+dx;
const next_y = e.pos_y+dy;
const destination_tile = self.getTile(next_x, next_y) orelse return false;
if (e.type == .player or e.type == .box) {
const destination_entity_id = destination_tile.entities_by_layer.get(e.layer);
if (self.entities.get(destination_entity_id)) |destination_entity| {
if (destination_entity.type == .box) {
if (!self.moveEntity(destination_entity_id, dx, dy)) {
return false;
}
}
}
}
return true;
return self.setEntityPosition(id, next_x, next_y);
}
};
const Level = struct {
number: i32,
world: World,
};
world: World,
current_level: u32 = 0,
levels: std.ArrayList(Level),
roboto_font: Font,
tilesheet: Tilesheet,
@ -246,80 +302,170 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
.tilesheet = tilesheet,
.footstep_buffer = footstep_sound,
.sfx_bus = Audio.addBus(.{ .label = "sfx" }),
.world = .{
.tilemap = undefined,
.entities = try .initCapacity(plt.gpa, 256)
}
.world = undefined,
.levels = .empty,
};
var player_pos: Vec2 = .zero;
{
var scratch = std.heap.ArenaAllocator.init(plt.gpa);
defer scratch.deinit();
self.levels = try self.loadLevels(plt.gpa, plt.assets);
self.world = try self.levels.items[0].world.clone(plt.gpa);
var buffers = Tiled.xml.Lexer.Buffers.init(plt.gpa);
defer buffers.deinit();
return null;
}
const tilemap = try Tiled.Tilemap.initFromBuffer(plt.gpa, &scratch, &buffers, plt.assets.readFile("tiled/map.tmx"));
fn loadLevels(self: *App, gpa: Allocator, assets: *Platform.Assets) !std.ArrayList(Level) {
var xml_parser = try Tiled.xml.Parser.initCapacity(gpa, .{});
defer xml_parser.deinit(gpa);
const tilemap = try Tiled.Tilemap.initFromXml(
gpa,
try xml_parser.parse(assets.readFile("tiled/map.tmx")),
.root
);
defer tilemap.deinit();
var tilesets: Tiled.Tileset.List = .empty;
defer tilesets.deinit(plt.gpa);
defer tilesets.deinit(gpa);
try tilesets.add(plt.gpa, "tileset.tsx", try Tiled.Tileset.initFromBuffer(plt.gpa, &scratch, &buffers, plt.assets.readFile("tiled/tileset.tsx")));
try tilesets.add(gpa, "tileset.tsx", try Tiled.Tileset.initFromXml(
gpa,
try xml_parser.parse(assets.readFile("tiled/tileset.tsx")),
.root
));
const bounds = tilemap.getTileBounds();
var levels: std.ArrayList(Level) = .empty;
self.world.tilemap = try Tilemap.init(plt.gpa, bounds.width(), bounds.height());
const level_bounds = tilemap.getLayer("Level bounds").?;
for (level_bounds.variant.object.items) |obj| {
if (obj.shape != .rectangle) {
continue;
}
const level_index = obj.properties.getInt("level") orelse {
log.warn("Level bounds doesn't have index", .{});
continue;
};
var rect = obj.shape.rectangle;
rect.x /= @floatFromInt(tilemap.tile_width);
rect.y /= @floatFromInt(tilemap.tile_height);
rect.width /= @floatFromInt(tilemap.tile_width);
rect.height /= @floatFromInt(tilemap.tile_height);
var level_bound = Tiled.Tilemap.Bounds{
.min_x = @intFromFloat(@floor(rect.x)),
.min_y = @intFromFloat(@floor(rect.y)),
.max_x = @intFromFloat(@ceil(rect.x + rect.width)),
.max_y = @intFromFloat(@ceil(rect.y + rect.height)),
};
level_bound = tilemap.trimBoundsByEmptyTiles(level_bound);
var level = try self.loadLevel(gpa, tilemap, tilesets, level_bound);
level.number = level_index;
try levels.append(gpa, level);
}
const Context = struct {
fn lessThan(_: @This(), lhs: Level, rhs: Level) bool {
return lhs.number < rhs.number;
}
};
std.mem.sort(Level, levels.items, Context{}, Context.lessThan);
return levels;
}
fn loadLevel(
self: *App,
gpa: Allocator,
tilemap: Tiled.Tilemap,
tilesets: Tiled.Tileset.List,
bounds: Tiled.Tilemap.Bounds
) !Level {
var level = Level{
.number = 0,
.world = .{
.tilemap = try .init(gpa, bounds.width(), bounds.height()),
.entities = try .initCapacity(gpa, 256)
}
};
const ground = tilemap.getLayer("Ground").?;
const walls = tilemap.getLayer("Walls").?;
const foreground = tilemap.getLayer("Foreground").?;
for (0..bounds.height()) |oy| {
for (0..bounds.width()) |ox| {
const x = bounds.min_x + ox;
const y = bounds.min_y + oy;
const x = bounds.min_x + @as(i32, @intCast(ox));
const y = bounds.min_y + @as(i32, @intCast(oy));
const tile = self.world.tilemap.get(ox, oy);
const tile = level.world.tilemap.get(ox, oy);
if (tilemap.getTile(ground, tilesets, x, y)) |tiled_tile| {
const pos = tiled_tile.getPosition();
tile.ground_sprite = tilesheet.get(pos.x, pos.y);
tile.ground_sprite = self.tilesheet.get(pos.x, pos.y);
}
if (tilemap.getTile(walls, tilesets, x, y)) |tiled_tile| {
if (tilemap.getTile(foreground, tilesets, x, y)) |tiled_tile| {
const pos = tiled_tile.getPosition();
const wall_id = try self.world.spawnWall(tilesheet.get(pos.x, pos.y));
_ = self.world.moveEntity(wall_id, @intCast(ox), @intCast(oy));
}
}
const props = tiled_tile.getProperties();
const props_type = props.getString("type") orelse "";
var entity_type: ?Entity.Type = null;
var layer: Entity.Layer = .surface;
if (std.mem.eql(u8, props_type, "wall")) {
entity_type = .wall;
} else if (std.mem.eql(u8, props_type, "box")) {
entity_type = .box;
} else if (std.mem.eql(u8, props_type, "goal_marker")) {
entity_type = .goal_marker;
layer = .ground;
} else {
log.warn("Unknown tile type: '{s}'", .{props_type});
}
const markers = &tilemap.getLayer("Markers").?.variant.object;
const spawn = markers.getByName("spawn").?;
player_pos.x = @divFloor(spawn.shape.point.x, @as(f32, @floatFromInt(tilemap.tile_width)));
player_pos.x -= @floatFromInt(bounds.min_x);
player_pos.y = @divFloor(spawn.shape.point.y, @as(f32, @floatFromInt(tilemap.tile_height)));
player_pos.y -= @floatFromInt(bounds.min_y);
const boxes = tilemap.getLayer("Boxes").?;
for (0..bounds.height()) |oy| {
for (0..bounds.width()) |ox| {
const x = bounds.min_x + ox;
const y = bounds.min_y + oy;
if (tilemap.getTile(boxes, tilesets, x, y)) |tiled_tile| {
const pos = tiled_tile.getPosition();
const box_id = try self.world.spawnBox(tilesheet.get(pos.x, pos.y));
_ = self.world.moveEntity(box_id, @intCast(ox), @intCast(oy));
if (entity_type != null) {
_ = level.world.spawnEntity(Entity{
.type = entity_type.?,
.layer = layer,
.pos_x = @intCast(ox),
.pos_y = @intCast(oy),
.sprite = self.tilesheet.get(pos.x, pos.y)
});
}
}
}
}
const player_id = try self.world.spawnPlayer();
_ = self.world.moveEntity(player_id, @intFromFloat(player_pos.x), @intFromFloat(player_pos.y));
for (tilemap.getLayer("Markers").?.variant.object.items) |obj| {
if (obj.shape != .point) {
continue;
}
if (!std.mem.eql(u8, obj.name, "spawn")) {
continue;
}
return null;
var position = obj.shape.point;
position.x /= @floatFromInt(tilemap.tile_width);
position.y /= @floatFromInt(tilemap.tile_height);
const pos_x: i32 = @intFromFloat(position.x);
const pos_y: i32 = @intFromFloat(position.y);
if (bounds.isInside(pos_x, pos_y)) {
_ = level.world.spawnEntity(Entity{
.type = .player,
.layer = .surface,
.pos_x = pos_x - bounds.min_x,
.pos_y = pos_y - bounds.min_y
});
}
}
return level;
}
fn updateWorld(world: *World) void {
_ = world; // autofix
}
pub fn frame(self: *App, plt: Platform.Frame) !void {
@ -354,21 +500,14 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
canvas.push();
defer canvas.pop(clear_color);
for (0..self.world.tilemap.height) |y| {
for (0..self.world.tilemap.width) |x| {
const tile = self.world.tilemap.get(x, y);
if (tile.ground_sprite) |sprite| {
Gfx.drawSprite(sprite, Vec2.initFromInt(usize, x, y).multiply(tile_size), tile_size, .white);
}
}
}
var goal_count: u32 = 0;
var box_at_goal_count: u32 = 0;
{
var entity_iter = self.world.entities.iterator();
while (entity_iter.next()) |entity_id| {
const entity = self.world.entities.getAssumeExists(entity_id);
if (entity.type == .player) {
if ((dir_x != 0 or dir_y != 0) and self.world.movePlayer(entity_id, dir_x, dir_y)) {
if ((dir_x != 0 or dir_y != 0) and self.world.moveEntity(entity_id, dir_x, dir_y)) {
var new_direction = entity.player_direction;
if (dir_x < 0) {
new_direction = .left;
@ -403,9 +542,38 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
}
entity.sprite = frames[entity.player_frame_index];
} else if (entity.type == .goal_marker) {
goal_count += 1;
const tile = self.world.getTile(entity.pos_x, entity.pos_y).?;
if (self.world.entities.get(tile.entities_by_layer.get(.surface))) |above_entity| {
if (above_entity.type == .box) {
box_at_goal_count += 1;
}
}
}
entity.visual_pos = .initFromInt(i32, entity.pos_x, entity.pos_y);
}
}
for (0..self.world.tilemap.height) |y| {
for (0..self.world.tilemap.width) |x| {
const tile = self.world.tilemap.get(x, y);
if (tile.ground_sprite) |sprite| {
Gfx.drawSprite(sprite, Vec2.initFromInt(usize, x, y).multiply(tile_size), tile_size, .white);
}
}
}
inline for (.{ Entity.Layer.ground, Entity.Layer.surface }) |layer| {
var entity_iter = self.world.entities.iterator();
while (entity_iter.next()) |entity_id| {
const entity = self.world.entities.getAssumeExists(entity_id);
if (entity.layer != layer) {
continue;
}
if (entity.sprite) |sprite| {
Gfx.drawSprite(
@ -416,18 +584,23 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
);
}
}
}
// self.roboto_font.drawText(.{
// .text = try std.fmt.allocPrint(plt.frame, "Hello {:.0}!", .{plt.time()}),
// .pos = .init(0, 0),
// .height = 32,
// });
if (goal_count > 0 and goal_count == box_at_goal_count and self.current_level+1 < self.levels.items.len) {
self.current_level += 1;
self.world.deinit(plt.gpa);
self.world = try self.levels.items[self.current_level].world.clone(plt.gpa);
}
}
pub fn deinit(self: *App, plt: Platform.Deinit) void {
self.roboto_font.deinit(plt.gpa);
Gfx.deinitSpritesheet(self.roboto_font.spritesheet);
self.world.tilemap.deinit(plt.gpa);
self.world.entities.slots.deinit(plt.gpa);
self.world.deinit(plt.gpa);
for (self.levels.items) |*level| {
level.world.deinit(plt.gpa);
}
self.levels.deinit(plt.gpa);
}

View File

@ -1,10 +1,9 @@
const Math = @import("math");
const Vec2 = Math.Vec2;
const Platform = @import("platform/root.zig");
const Platform = @import("platform");
const Gfx = Platform.Gfx;
const Color = Platform.Color;
const Vec2 = Platform.Math.Vec2;
const Canvas = @This();
size: Vec2,

View File

@ -4,7 +4,7 @@ const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Io = std.Io;
const Platform = @import("platform/root.zig");
const Platform = @import("platform");
const Gfx = Platform.Gfx;
const SlotMapType = Platform.SlotMapType;
const Vec2 = Platform.Vec2;
@ -92,7 +92,7 @@ pub fn getGlyph(font: *Font, index: Glyph.Index, scale_x: f32, scale_y: f32) ?*F
assert(box_height < Font.Glyph.max_height);
var bitmap_buffer: [Font.Glyph.max_width * Font.Glyph.max_height]u8 = undefined;
const bitmap = Gfx.ImageData{
const bitmap = Gfx.Image{
.pixels = .{ .r8 = (&bitmap_buffer).ptr },
.width = box_width,
.height = box_height
@ -418,8 +418,8 @@ const GlyphCache = struct {
defer cache.deinit(gpa);
const key = Glyph.Key{ .index = 10, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
const id1 = cache.insert(key).?;
const id2 = cache.insert(key).?;
const id1 = cache.insertOrGet(key).?.id;
const id2 = cache.insertOrGet(key).?.id;
try std.testing.expectEqual(id1, id2);
}
@ -429,9 +429,9 @@ const GlyphCache = struct {
defer cache.deinit(gpa);
const key = Glyph.Key{ .index = 10, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
const id1 = cache.insert(key).?;
const id1 = cache.insertOrGet(key).?.id;
cache.remove(id1);
const id2 = cache.insert(key).?;
const id2 = cache.insertOrGet(key).?.id;
try std.testing.expectEqual(id1, id2);
}
@ -440,8 +440,8 @@ const GlyphCache = struct {
var cache = try GlyphCache.init(gpa, 256);
defer cache.deinit(gpa);
const id1 = cache.insert(Glyph.Key{ .index = 10, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
const id2 = cache.insert(Glyph.Key{ .index = 11, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
const id1 = cache.insertOrGet(Glyph.Key{ .index = 10, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
const id2 = cache.insertOrGet(Glyph.Key{ .index = 11, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
try std.testing.expect(id1 != id2);
}
@ -452,10 +452,10 @@ const GlyphCache = struct {
defer cache.deinit(gpa);
for (0..limit-1) |i| {
const id = cache.insert(Glyph.Key{ .index = @intCast(i), .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
const id = cache.insertOrGet(Glyph.Key{ .index = @intCast(i), .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
try std.testing.expect(id != null);
}
const id = cache.insert(Glyph.Key{ .index = limit, .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
const id = cache.insertOrGet(Glyph.Key{ .index = limit, .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
try std.testing.expect(id == null);
}
@ -470,9 +470,9 @@ const GlyphCache = struct {
defer ids.deinit(gpa);
for (0..(limit-1)) |i| {
const id = cache.insert(Glyph.Key{ .index = @intCast(i), .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
const id = cache.insertOrGet(Glyph.Key{ .index = @intCast(i), .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
try std.testing.expect(id != null);
try ids.append(gpa, id.?);
try ids.append(gpa, id.?.id);
}
assert(cache.free_head == GlyphSlot.nil_id);
@ -498,9 +498,9 @@ const GlyphCache = struct {
var cache = try GlyphCache.init(gpa, 256);
defer cache.deinit(gpa);
const id1 = cache.insert(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
const id2 = cache.insert(.{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
const id3 = cache.insert(.{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
const id1 = cache.insertOrGet(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
const id2 = cache.insertOrGet(.{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
const id3 = cache.insertOrGet(.{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
const lru = try cache.getLRUItems(gpa);
defer gpa.free(lru);
@ -513,9 +513,9 @@ const GlyphCache = struct {
var cache = try GlyphCache.init(gpa, 256);
defer cache.deinit(gpa);
const id1 = cache.insert(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
const id2 = cache.insert(.{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
const id3 = cache.insert(.{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
const id1 = cache.insertOrGet(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
const id2 = cache.insertOrGet(.{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
const id3 = cache.insertOrGet(.{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
const lru = try cache.getLRUItems(gpa);
defer gpa.free(lru);
@ -528,7 +528,7 @@ const GlyphCache = struct {
var cache = try GlyphCache.init(gpa, 256);
defer cache.deinit(gpa);
const id = cache.insert(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
const id = cache.insertOrGet(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
try std.testing.expect(cache.lru_head != GlyphSlot.nil_id);
try std.testing.expect(cache.lru_tail != GlyphSlot.nil_id);
cache.remove(id);
@ -542,13 +542,13 @@ const GlyphCache = struct {
defer cache.deinit(gpa);
const key1 = Glyph.Key{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
const id1 = cache.insert(key1).?;
const id1 = cache.insertOrGet(key1).?.id;
const key2 = Glyph.Key{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
const id2 = cache.insert(key2).?;
const id2 = cache.insertOrGet(key2).?.id;
const key3 = Glyph.Key{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
const id3 = cache.insert(key3).?;
const id3 = cache.insertOrGet(key3).?.id;
_ = cache.lookup(key2);
_ = cache.insertOrGet(key2);
{
const lru = try cache.getLRUItems(gpa);
@ -556,7 +556,7 @@ const GlyphCache = struct {
try std.testing.expectEqualSlices(GlyphSlot.Id, &.{ id2, id3, id1 }, lru);
}
_ = cache.lookup(key1);
_ = cache.insertOrGet(key1);
{
const lru = try cache.getLRUItems(gpa);
@ -570,9 +570,9 @@ const GlyphCache = struct {
var cache = try GlyphCache.init(gpa, 256);
defer cache.deinit(gpa);
const id1 = cache.insert(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
const id2 = cache.insert(.{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
const id3 = cache.insert(.{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
const id1 = cache.insertOrGet(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
const id2 = cache.insertOrGet(.{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
const id3 = cache.insertOrGet(.{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
{
const lru = try cache.getLRUItems(gpa);

View File

@ -1,6 +1,6 @@
const std = @import("std");
const Platform = @import("platform/root.zig");
const Platform = @import("platform");
const App = @import("app.zig");
pub const std_options: std.Options = .{
@ -21,5 +21,5 @@ pub fn main(init: std.process.Init) void {
test {
std.testing.refAllDecls(@This());
_ = @import("./platform/slot_map.zig");
_ = Platform;
}

View File

@ -15,7 +15,6 @@ const Mat4 = Math.Mat4;
const Rect = Math.Rect;
const STBRectPack = @import("stb_rect_pack");
const STBTrueType = @import("stb_truetype");
const ImGUI = @import("imgui.zig");
pub const Color = @import("./color.zig");
const shd = @import("shader");
@ -126,7 +125,7 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
bindings.vertex_buffers[0] = sg.makeBuffer(.{
.size = @sizeOf(VertexQuad) * max_quads,
.usage = .{ .vertex_buffer = true, .stream_update = true },
.usage = .{ .vertex_buffer = true, .dynamic_update = true },
.label = "quad-vertices"
});

View File

@ -2,7 +2,7 @@ const std = @import("std");
const log = std.log.scoped(.app);
const Allocator = std.mem.Allocator;
const Platform = @import("platform/root.zig");
const Platform = @import("platform");
const Gfx = Platform.Gfx;
const Vec2 = Platform.Vec2;
const Rect = Platform.Rect;