Compare commits
No commits in common. "7632329c92e2263bdc155c1783990d5aad130185" and "0ac0cd0c3755dae3461e6792df5d8c744c608ced" have entirely different histories.
7632329c92
...
0ac0cd0c37
BIN
assets/sokoban/Preview.png
Normal file
BIN
assets/sokoban/Preview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 171 KiB |
BIN
assets/sokoban/Preview_KenneyNL.png
Normal file
BIN
assets/sokoban/Preview_KenneyNL.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 153 KiB |
@ -1,104 +1,81 @@
|
||||
<?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="1" nextlayerid="8" nextobjectid="19">
|
||||
<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">
|
||||
<tileset firstgid="1" source="tileset.tsx"/>
|
||||
<layer id="2" name="Ground" width="30" height="20">
|
||||
<data encoding="csv">
|
||||
<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>
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,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>
|
||||
</layer>
|
||||
<layer id="1" name="Foreground" width="30" height="20">
|
||||
<layer id="1" name="Walls" width="30" height="20">
|
||||
<data encoding="csv">
|
||||
<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>
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0,0,0,0,0,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>
|
||||
</layer>
|
||||
<objectgroup id="5" name="Markers">
|
||||
<object id="1" name="spawn" x="484" y="555">
|
||||
<object id="1" name="spawn" x="479" y="550">
|
||||
<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>
|
||||
|
||||
@ -13,8 +13,7 @@
|
||||
}
|
||||
},
|
||||
"tileset.tsx": {
|
||||
"scaleInDock": 0.5,
|
||||
"scaleInEditor": 1
|
||||
"scaleInDock": 1
|
||||
}
|
||||
},
|
||||
"last.imagePath": "/home/rokas/code/games/sokol-template-v2/assets/sokoban",
|
||||
@ -25,9 +24,7 @@
|
||||
"map.tmx"
|
||||
],
|
||||
"project": "sokoban.tiled-project",
|
||||
"property.type": "int",
|
||||
"recentFiles": [
|
||||
"tileset.tsx",
|
||||
"map.tmx"
|
||||
],
|
||||
"tileset.lastUsedFormat": "tsx",
|
||||
|
||||
@ -1,74 +1,4 @@
|
||||
<?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>
|
||||
|
||||
75
build.zig
75
build.zig
@ -1,13 +1,10 @@
|
||||
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",
|
||||
@ -37,39 +34,29 @@ 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);
|
||||
platform_mod.addOptions("build_options", build_options);
|
||||
exe_mod.addOptions("build_options", build_options);
|
||||
|
||||
platform_mod.addImport("embedded_assets", try createEmbeddedAssets(b, assets_dir, &asset_files));
|
||||
exe_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")
|
||||
});
|
||||
platform_mod.addImport("math", math_mod);
|
||||
exe_mod.addImport("math", math_mod);
|
||||
|
||||
dep_sokol = b.dependency("sokol", .{
|
||||
const dep_tiled = b.dependency("tiled", .{});
|
||||
exe_mod.addImport("tiled", dep_tiled.module("tiled"));
|
||||
|
||||
const dep_sokol = b.dependency("sokol", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.with_sokol_imgui = has_imgui
|
||||
});
|
||||
const mod_sokol = dep_sokol.module("sokol");
|
||||
platform_mod.linkLibrary(dep_sokol.artifact("sokol_clib"));
|
||||
platform_mod.addImport("sokol", mod_sokol);
|
||||
exe_mod.linkLibrary(dep_sokol.artifact("sokol_clib"));
|
||||
exe_mod.addImport("sokol", mod_sokol);
|
||||
|
||||
const dep_shdc = dep_sokol.builder.dependency("shdc", .{});
|
||||
|
||||
@ -89,7 +76,7 @@ pub fn build(b: *std.Build) !void {
|
||||
.slang = slang
|
||||
});
|
||||
mod_shader.addImport("math", math_mod);
|
||||
platform_mod.addImport("shader", mod_shader);
|
||||
exe_mod.addImport("shader", mod_shader);
|
||||
|
||||
if (has_imgui) {
|
||||
if (b.lazyDependency("cimgui", .{
|
||||
@ -98,13 +85,16 @@ pub fn build(b: *std.Build) !void {
|
||||
})) |dep_cimgui| {
|
||||
const cimgui = b.lazyImport(@This(), "cimgui").?;
|
||||
const cimgui_conf = cimgui.getConfig(true);
|
||||
platform_mod.addImport("cimgui", dep_cimgui.module(cimgui_conf.module_name));
|
||||
exe_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));
|
||||
}
|
||||
}
|
||||
|
||||
platform_mod.addImport("stb_vorbis", dep_stb.module("stb_vorbis"));
|
||||
platform_mod.addImport("stb_rect_pack", dep_stb.module("stb_rect_pack"));
|
||||
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"));
|
||||
|
||||
const dep_tracy = b.dependency("tracy", .{
|
||||
.target = target,
|
||||
@ -113,31 +103,32 @@ pub fn build(b: *std.Build) !void {
|
||||
.tracy_only_localhost = true
|
||||
});
|
||||
if (has_tracy) {
|
||||
platform_mod.linkLibrary(dep_tracy.artifact("tracy"));
|
||||
}
|
||||
platform_mod.addImport("tracy", dep_tracy.module("tracy"));
|
||||
exe_mod.linkLibrary(dep_tracy.artifact("tracy"));
|
||||
}
|
||||
exe_mod.addImport("tracy", dep_tracy.module("tracy"));
|
||||
|
||||
var run_cmd_step: *std.Build.Step = undefined;
|
||||
if (is_wasm) {
|
||||
@panic("TODO: Test wasm support on 0.17");
|
||||
if (true) {
|
||||
@panic("TODO: test in 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 = name,
|
||||
.name = "sokol_template_v2",
|
||||
.root_module = exe_mod
|
||||
});
|
||||
b.installArtifact(exe);
|
||||
|
||||
@ -2,15 +2,11 @@
|
||||
.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#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
|
||||
.url = "git+https://github.com/floooh/sokol-zig.git#ba97ac4e82e36a0f6c62f7fad5dc9c2d82beadd8",
|
||||
.hash = "sokol-0.1.0-pb1HK4prNwDGx2RJFyv7VtYJubDahi19XKioPzPeMCxv",
|
||||
},
|
||||
.stb = .{
|
||||
.path = "./libs/stb",
|
||||
@ -21,6 +17,11 @@
|
||||
.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",
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const Property = @import("./property.zig");
|
||||
const xml = @import("./xml.zig");
|
||||
@ -10,106 +9,43 @@ 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 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);
|
||||
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");
|
||||
|
||||
var split_iter = std.mem.splitScalar(u8, csv, ',');
|
||||
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, ',');
|
||||
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);
|
||||
cells.appendAssumeCapacity(tile_id);
|
||||
try temp_tiles.append(scratch.allocator(), 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;
|
||||
}
|
||||
|
||||
return TileVariant{
|
||||
.width = layer_width,
|
||||
.height = layer_height,
|
||||
.chunks = chunks.items
|
||||
};
|
||||
try iter.finish("data");
|
||||
|
||||
return try arena.dupe(u32, temp_tiles.items);
|
||||
}
|
||||
|
||||
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))];
|
||||
}
|
||||
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];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -123,9 +59,9 @@ pub const ImageVariant = struct {
|
||||
width: ?u32,
|
||||
height: ?u32,
|
||||
|
||||
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;
|
||||
fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Image {
|
||||
var iter = xml.TagParser.init(lexer);
|
||||
const attrs = try iter.begin("image");
|
||||
|
||||
// TODO: format
|
||||
const source = try attrs.getDupe(arena, "source") orelse return error.MissingSource;
|
||||
@ -133,6 +69,8 @@ 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,
|
||||
@ -188,6 +126,15 @@ 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) {
|
||||
@ -212,13 +159,16 @@ variant: Variant,
|
||||
|
||||
pub fn initFromXml(
|
||||
arena: std.mem.Allocator,
|
||||
doc: xml.Document,
|
||||
element_id: xml.Element.Id
|
||||
scratch: *std.heap.ArenaAllocator,
|
||||
lexer: *xml.Lexer,
|
||||
) !Layer {
|
||||
const tag = doc.getTag(element_id) orelse return error.TagNotFound;
|
||||
const value = try lexer.peek() orelse return error.MissingStartTag;
|
||||
if (value != .start_tag) return error.MissingStartTag;
|
||||
|
||||
const layer_type = Type.name_map.get(tag.variant.tag.name) orelse return error.UnknownLayerType;
|
||||
const attrs = tag.variant.tag.attributes;
|
||||
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 id = try attrs.getNumber(u32, "id") orelse return error.MissingId;
|
||||
const name = try attrs.getDupe(arena, "name") orelse "";
|
||||
@ -234,12 +184,14 @@ pub fn initFromXml(
|
||||
var variant: Variant = undefined;
|
||||
switch (layer_type) {
|
||||
.tile => {
|
||||
variant = Variant{
|
||||
.tile = try TileVariant.initFromXml(
|
||||
arena,
|
||||
doc,
|
||||
element_id
|
||||
)
|
||||
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{}
|
||||
}
|
||||
};
|
||||
},
|
||||
.image => {
|
||||
@ -278,41 +230,45 @@ 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) {
|
||||
try objects.ensureTotalCapacityPrecise(arena, doc.countChildren(element_id));
|
||||
variant.object.items = try arena.dupe(Object, objects.items);
|
||||
}
|
||||
|
||||
if (variant == .group) {
|
||||
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;
|
||||
variant.group.layers = try arena.dupe(Layer, layers.items);
|
||||
}
|
||||
|
||||
return Layer{
|
||||
@ -330,3 +286,7 @@ 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");
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ 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();
|
||||
|
||||
@ -114,15 +113,14 @@ 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,
|
||||
doc: xml.Document,
|
||||
element_id: xml.Element.Id
|
||||
scratch: *std.heap.ArenaAllocator,
|
||||
lexer: *xml.Lexer,
|
||||
) !Object {
|
||||
const tag = try doc.getTagExpect(element_id, "object");
|
||||
const attrs = tag.variant.tag.attributes;
|
||||
var iter = xml.TagParser.init(lexer);
|
||||
const attrs = try iter.begin("object");
|
||||
|
||||
const id = try attrs.getNumber(u32, "id") orelse return error.MissingId;
|
||||
const name = try attrs.getDupe(arena, "name") orelse "";
|
||||
@ -136,14 +134,9 @@ pub fn initFromXml(
|
||||
|
||||
var shape: ?Shape = null;
|
||||
|
||||
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;
|
||||
|
||||
while (try iter.next()) |node| {
|
||||
if (shape == null) {
|
||||
if (std.mem.eql(u8, child_name, "point")) {
|
||||
if (node.isTag("point")) {
|
||||
shape = .{
|
||||
.point = Shape.Point{
|
||||
.x = x,
|
||||
@ -151,7 +144,7 @@ pub fn initFromXml(
|
||||
}
|
||||
};
|
||||
|
||||
} else if (std.mem.eql(u8, child_name, "ellipse")) {
|
||||
} else if (node.isTag("ellipse")) {
|
||||
shape = .{
|
||||
.ellipse = Shape.Ellipse{
|
||||
.x = x,
|
||||
@ -161,11 +154,11 @@ pub fn initFromXml(
|
||||
}
|
||||
};
|
||||
|
||||
} else if (std.mem.eql(u8, child_name, "text")) {
|
||||
} else if (node.isTag("text")) {
|
||||
const HorizontalShape = Shape.Text.HorizontalAlign;
|
||||
const VerticalAlign = Shape.Text.VerticalAlign;
|
||||
|
||||
const text_attrs = child_attrs;
|
||||
const text_attrs = node.tag.attributes;
|
||||
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;
|
||||
@ -178,13 +171,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 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);
|
||||
}
|
||||
const content_value = try lexer.peek();
|
||||
if (content_value != null and content_value.? == .text) {
|
||||
content = try arena.dupe(u8, content_value.?.text);
|
||||
}
|
||||
try lexer.skipUntilMatchingEndTag("text");
|
||||
|
||||
shape = .{
|
||||
.text = Shape.Text{
|
||||
@ -210,16 +203,14 @@ pub fn initFromXml(
|
||||
};
|
||||
continue;
|
||||
|
||||
} else if (std.mem.eql(u8, child_name, "polygon")) {
|
||||
const points_str = child_attrs.get("points") orelse "";
|
||||
} else if (node.isTag("polygon")) {
|
||||
const points_str = node.tag.attributes.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);
|
||||
points.appendAssumeCapacity(point);
|
||||
try points.append(scratch.allocator(), point);
|
||||
}
|
||||
|
||||
shape = .{
|
||||
@ -232,6 +223,7 @@ pub fn initFromXml(
|
||||
}
|
||||
}
|
||||
|
||||
try iter.skip();
|
||||
}
|
||||
|
||||
if (shape == null) {
|
||||
@ -257,7 +249,7 @@ pub fn initFromXml(
|
||||
}
|
||||
}
|
||||
|
||||
const properties = try Property.List.initFromXmlContainer(arena, doc, element_id);
|
||||
try iter.finish("object");
|
||||
|
||||
return Object{
|
||||
.id = id,
|
||||
@ -265,8 +257,7 @@ pub fn initFromXml(
|
||||
.class = class,
|
||||
.rotation = rotation,
|
||||
.visible = visible,
|
||||
.shape = shape orelse return error.UnknownShapeType,
|
||||
.properties = properties
|
||||
.shape = shape orelse return error.UnknownShapeType
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -33,48 +33,32 @@ pub const List = struct {
|
||||
|
||||
pub fn initFromXml(
|
||||
arena: std.mem.Allocator,
|
||||
doc: xml.Document,
|
||||
element_id: xml.Element.Id
|
||||
scratch: *std.heap.ArenaAllocator,
|
||||
lexer: *xml.Lexer
|
||||
) !Property.List {
|
||||
_ = try doc.getTagExpect(element_id, "properties");
|
||||
var iter = xml.TagParser.init(lexer);
|
||||
_ = try iter.begin("properties");
|
||||
|
||||
var properties: std.ArrayList(Property) = .empty;
|
||||
try properties.ensureTotalCapacityPrecise(arena, doc.countChildren(element_id));
|
||||
var temp_properties: std.ArrayList(Property) = .empty;
|
||||
|
||||
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);
|
||||
while (try iter.next()) |node| {
|
||||
if (node.isTag("property")) {
|
||||
const property = try Property.initFromXml(arena, lexer);
|
||||
try temp_properties.append(scratch.allocator(), property);
|
||||
continue;
|
||||
}
|
||||
|
||||
try iter.skip();
|
||||
}
|
||||
try iter.finish("properties");
|
||||
|
||||
const properties = try arena.dupe(Property, temp_properties.items);
|
||||
|
||||
return List{
|
||||
.items = properties.items
|
||||
.items = properties
|
||||
};
|
||||
}
|
||||
|
||||
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)) {
|
||||
@ -86,19 +70,8 @@ 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;
|
||||
}
|
||||
};
|
||||
@ -110,9 +83,9 @@ pub fn init(name: []const u8, value: Value) 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;
|
||||
pub fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Property {
|
||||
var iter = xml.TagParser.init(lexer);
|
||||
const attrs = try iter.begin("property");
|
||||
|
||||
const name = try attrs.getDupe(arena, "name") orelse return error.MissingName;
|
||||
const prop_type_str = attrs.get("type") orelse "string";
|
||||
@ -131,6 +104,8 @@ pub fn initFromXml(arena: std.mem.Allocator, doc: xml.Document, element_id: xml.
|
||||
}
|
||||
};
|
||||
|
||||
try iter.finish("property");
|
||||
|
||||
return Property{
|
||||
.name = name,
|
||||
.value = value
|
||||
|
||||
@ -12,5 +12,5 @@ pub const Tileset = @import("./tileset.zig");
|
||||
pub const Tilemap = @import("./tilemap.zig");
|
||||
|
||||
test {
|
||||
_ = std.testing.refAllDecls(@This());
|
||||
_ = std.testing.refAllDeclsRecursive(@This());
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
const std = @import("std");
|
||||
const log = std.log;
|
||||
const xml = @import("./xml.zig");
|
||||
const Io = std.Io;
|
||||
const assert = std.debug.assert;
|
||||
@ -55,13 +54,15 @@ pub const TilesetReference = struct {
|
||||
source: []const u8,
|
||||
first_gid: u32,
|
||||
|
||||
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;
|
||||
pub fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !TilesetReference {
|
||||
var iter = xml.TagParser.init(lexer);
|
||||
const attrs = try iter.begin("tileset");
|
||||
|
||||
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
|
||||
@ -105,20 +106,40 @@ 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,
|
||||
doc: xml.Document,
|
||||
element_id: xml.Element.Id
|
||||
scratch: *std.heap.ArenaAllocator,
|
||||
lexer: *xml.Lexer,
|
||||
) !Tilemap {
|
||||
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
||||
errdefer arena_allocator.deinit();
|
||||
|
||||
const arena = arena_allocator.allocator();
|
||||
|
||||
const tag = try doc.getTagExpect(element_id, "map");
|
||||
const map_attrs = tag.variant.tag.attributes;
|
||||
var iter = xml.TagParser.init(lexer);
|
||||
const map_attrs = try iter.begin("map");
|
||||
|
||||
const version = try map_attrs.getDupe(arena, "version") orelse return error.MissingVersion;
|
||||
const tiled_version = try map_attrs.getDupe(arena, "tiledversion");
|
||||
@ -144,43 +165,26 @@ 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);
|
||||
|
||||
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(
|
||||
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(
|
||||
arena,
|
||||
doc,
|
||||
child_id
|
||||
));
|
||||
scratch,
|
||||
lexer,
|
||||
);
|
||||
try layer_list.append(scratch.allocator(), layer);
|
||||
continue;
|
||||
}
|
||||
|
||||
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);
|
||||
@ -219,7 +223,7 @@ fn getTilesetByGid(self: *const Tilemap, gid: u32) ?TilesetReference {
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn getTile(self: *const Tilemap, layer: *const Layer, tilesets: Tileset.List, x: i32, y: i32) ?Tile {
|
||||
pub fn getTile(self: *const Tilemap, layer: *const Layer, tilesets: Tileset.List, x: usize, y: usize) ?Tile {
|
||||
assert(layer.variant == .tile);
|
||||
const tile_variant = layer.variant.tile;
|
||||
|
||||
@ -244,10 +248,10 @@ pub fn getLayer(self: *const Tilemap, name: []const u8) ?*const Layer {
|
||||
}
|
||||
|
||||
pub const Bounds = struct {
|
||||
min_x: i32,
|
||||
min_y: i32,
|
||||
max_x: i32,
|
||||
max_y: i32,
|
||||
min_x: usize,
|
||||
min_y: usize,
|
||||
max_x: usize,
|
||||
max_y: usize,
|
||||
|
||||
const zero = Bounds{
|
||||
.min_x = 0,
|
||||
@ -256,16 +260,12 @@ pub const Bounds = struct {
|
||||
.max_y = 0,
|
||||
};
|
||||
|
||||
pub fn width(self: Bounds) u32 {
|
||||
return @intCast(self.max_x - self.min_x + 1);
|
||||
pub fn width(self: Bounds) usize {
|
||||
return self.max_x - self.min_x + 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);
|
||||
pub fn height(self: Bounds) usize {
|
||||
return self.max_y - self.min_y + 1;
|
||||
}
|
||||
};
|
||||
|
||||
@ -277,22 +277,26 @@ pub fn getTileBounds(self: *const Tilemap) Bounds {
|
||||
continue;
|
||||
}
|
||||
|
||||
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))
|
||||
};
|
||||
for (0..self.height) |y| {
|
||||
for (0..self.width) |x| {
|
||||
const gid = layer.variant.tile.get(x, y).?;
|
||||
if (gid != 0) {
|
||||
if (result) |bounds| {
|
||||
result = Bounds{
|
||||
.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)
|
||||
.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)
|
||||
};
|
||||
} else {
|
||||
result = chunk_bounds;
|
||||
result = Bounds{
|
||||
.min_x = x,
|
||||
.min_y = y,
|
||||
.max_x = x,
|
||||
.max_y = y
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -300,59 +304,6 @@ 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();
|
||||
}
|
||||
|
||||
@ -14,14 +14,16 @@ pub const Image = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
|
||||
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;
|
||||
pub fn intFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Image {
|
||||
var iter = xml.TagParser.init(lexer);
|
||||
const attrs = try iter.begin("image");
|
||||
|
||||
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,
|
||||
@ -36,14 +38,26 @@ pub const Tile = struct {
|
||||
|
||||
pub fn initFromXml(
|
||||
arena: std.mem.Allocator,
|
||||
doc: xml.Document,
|
||||
element_id: xml.Element.Id
|
||||
scratch: *std.heap.ArenaAllocator,
|
||||
lexer: *xml.Lexer,
|
||||
) !Tile {
|
||||
const tag = try doc.getTagExpect(element_id, "tile");
|
||||
const tile_attrs = tag.variant.tag.attributes;
|
||||
var iter = xml.TagParser.init(lexer);
|
||||
const tile_attrs = try iter.begin("tile");
|
||||
|
||||
const id = try tile_attrs.getNumber(u32, "id") orelse return error.MissingId;
|
||||
const properties = try Property.List.initFromXmlContainer(arena, doc, element_id);
|
||||
|
||||
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");
|
||||
|
||||
return Tile{
|
||||
.id = id,
|
||||
@ -110,16 +124,36 @@ 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,
|
||||
doc: xml.Document,
|
||||
element_id: xml.Element.Id
|
||||
scratch: *std.heap.ArenaAllocator,
|
||||
lexer: *xml.Lexer
|
||||
) !Tileset {
|
||||
var arena_state = std.heap.ArenaAllocator.init(gpa);
|
||||
const arena = arena_state.allocator();
|
||||
|
||||
const tag = try doc.getTagExpect(element_id, "tileset");
|
||||
const tileset_attrs = tag.variant.tag.attributes;
|
||||
var iter = xml.TagParser.init(lexer);
|
||||
const tileset_attrs = try iter.begin("tileset");
|
||||
|
||||
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;
|
||||
@ -131,20 +165,20 @@ pub fn initFromXml(
|
||||
|
||||
var image: ?Image = null;
|
||||
var tiles_list: std.ArrayList(Tile) = .empty;
|
||||
try tiles_list.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;
|
||||
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;
|
||||
}
|
||||
|
||||
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.skip();
|
||||
}
|
||||
try iter.finish("tileset");
|
||||
|
||||
const tiles = try arena.dupe(Tile, tiles_list.items);
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const Color = @import("./color.zig");
|
||||
@ -106,160 +105,102 @@ pub const Attribute = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const Element = struct {
|
||||
variant: union(enum) {
|
||||
tag: struct {
|
||||
pub const Tag = struct {
|
||||
name: []const u8,
|
||||
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);
|
||||
};
|
||||
attributes: Attribute.List
|
||||
};
|
||||
|
||||
pub const Document = struct {
|
||||
elements: []Element,
|
||||
pub const Lexer = struct {
|
||||
pub const Buffers = struct {
|
||||
scratch: std.heap.ArenaAllocator,
|
||||
text: std.ArrayList(u8),
|
||||
|
||||
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 init(allocator: std.mem.Allocator) Buffers {
|
||||
return Buffers{
|
||||
.scratch = std.heap.ArenaAllocator.init(allocator),
|
||||
.text = .empty
|
||||
};
|
||||
}
|
||||
|
||||
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 clear(self: *Buffers) void {
|
||||
self.text.clearRetainingCapacity();
|
||||
_ = self.scratch.reset(.retain_capacity);
|
||||
}
|
||||
|
||||
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;
|
||||
pub fn deinit(self: *Buffers) void {
|
||||
const allocator = self.scratch.child_allocator;
|
||||
self.scratch.deinit();
|
||||
self.text.deinit(allocator);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const Lexer = struct {
|
||||
pub const Token = union(enum) {
|
||||
start_tag_begin: []const u8,
|
||||
attribute: Attribute,
|
||||
start_tag_end: []const u8,
|
||||
start_tag: Tag,
|
||||
end_tag: []const u8,
|
||||
text: []const u8,
|
||||
// TODO: Add comment type?
|
||||
|
||||
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}),
|
||||
pub fn isStartTag(self: Token, name: []const u8) bool {
|
||||
if (self == .start_tag) {
|
||||
return std.mem.eql(u8, self.start_tag.name, name);
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
text: []const u8,
|
||||
cursor: usize = 0,
|
||||
inside_start_tag: ?[]const u8 = null,
|
||||
queued_end_tag: ?[]const u8 = null,
|
||||
pub const StepResult = struct {
|
||||
token: ?Token,
|
||||
self_closing: bool = false,
|
||||
};
|
||||
|
||||
pub fn next(self: *Lexer) !?Token {
|
||||
if (self.queued_end_tag) |name| {
|
||||
self.queued_end_tag = null;
|
||||
return Token{
|
||||
.end_tag = name
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
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();
|
||||
fn step(self: *Lexer) !StepResult {
|
||||
_ = self.buffers.scratch.reset(.retain_capacity);
|
||||
|
||||
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() == '<') {
|
||||
if (try self.peekByte() == '<') {
|
||||
self.tossByte();
|
||||
|
||||
if (try self.peekByte() == '/') {
|
||||
@ -273,7 +214,8 @@ const Lexer = struct {
|
||||
return error.InvalidEndTag;
|
||||
}
|
||||
|
||||
return Token{ .end_tag = name };
|
||||
const token = Token{ .end_tag = name };
|
||||
return .{ .token = token };
|
||||
|
||||
} else if (try self.peekByte() == '?') {
|
||||
// Prolog tag
|
||||
@ -282,14 +224,9 @@ const Lexer = struct {
|
||||
return error.InvalidPrologTag;
|
||||
}
|
||||
|
||||
var attributes_buf: [8]Attribute = undefined;
|
||||
const attributes_len = try self.parseAttributesBounded(&attributes_buf);
|
||||
const attributes = try self.parseAttributes();
|
||||
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;
|
||||
}
|
||||
@ -303,31 +240,104 @@ const Lexer = struct {
|
||||
return error.InvalidPrologEncoding;
|
||||
}
|
||||
|
||||
return .{ .token = null };
|
||||
|
||||
} else {
|
||||
// Start tag
|
||||
const name = try self.parseName();
|
||||
self.inside_start_tag = name;
|
||||
const attributes = try self.parseAttributes();
|
||||
try self.skipWhiteSpace();
|
||||
|
||||
return Token{
|
||||
.start_tag_begin = 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
|
||||
};
|
||||
}
|
||||
|
||||
} 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.text[text_start..self.cursor];
|
||||
var text: []const u8 = self.buffers.text.items[text_start..self.cursor];
|
||||
text = std.mem.trimEnd(u8, text, &std.ascii.whitespace);
|
||||
|
||||
var token: ?Token = null;
|
||||
if (text.len > 0) {
|
||||
return Token{ .text = text };
|
||||
token = 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -371,6 +381,45 @@ 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);
|
||||
}
|
||||
@ -380,14 +429,16 @@ const Lexer = struct {
|
||||
}
|
||||
|
||||
fn hasBytes(self: *Lexer, n: usize) bool {
|
||||
return self.cursor + n <= self.text.len;
|
||||
const text = self.buffers.text.items;
|
||||
return self.cursor + n <= text.len;
|
||||
}
|
||||
|
||||
fn peekBytes(self: *Lexer, n: usize) ![]const u8 {
|
||||
if (self.hasBytes(n)) {
|
||||
return self.text[self.cursor..][0..n];
|
||||
const text = self.buffers.text.items;
|
||||
return text[self.cursor..][0..n];
|
||||
}
|
||||
return error.UnexpectedEOF;
|
||||
return error.EndOfTextBuffer;
|
||||
}
|
||||
|
||||
fn tossBytes(self: *Lexer, n: usize) void {
|
||||
@ -424,11 +475,11 @@ const Lexer = struct {
|
||||
}
|
||||
}
|
||||
|
||||
return self.text[name_start..self.cursor];
|
||||
return self.buffers.text.items[name_start..self.cursor];
|
||||
}
|
||||
|
||||
fn skipWhiteSpace(self: *Lexer) !void {
|
||||
while (self.cursor < self.text.len and std.ascii.isWhitespace(try self.peekByte())) {
|
||||
while (std.ascii.isWhitespace(try self.peekByte())) {
|
||||
self.tossByte();
|
||||
}
|
||||
}
|
||||
@ -453,14 +504,18 @@ const Lexer = struct {
|
||||
value_len += 1;
|
||||
}
|
||||
|
||||
return self.text[value_start..][0..value_len];
|
||||
return self.buffers.text.items[value_start..][0..value_len];
|
||||
}
|
||||
|
||||
fn parseAttribute(self: *Lexer) !?Attribute {
|
||||
fn parseAttributes(self: *Lexer) !Attribute.List {
|
||||
const arena = self.buffers.scratch.allocator();
|
||||
var attributes: std.ArrayList(Attribute) = .empty;
|
||||
|
||||
while (true) {
|
||||
try self.skipWhiteSpace();
|
||||
const name = try self.parseName();
|
||||
if (name.len == 0) {
|
||||
return null;
|
||||
break;
|
||||
}
|
||||
|
||||
try self.skipWhiteSpace();
|
||||
@ -470,287 +525,163 @@ 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(attr.name) != null) {
|
||||
if (list.get(name) != null) {
|
||||
return error.DuplicateAttribute;
|
||||
}
|
||||
|
||||
try attributes.appendBounded(attr);
|
||||
try attributes.append(arena, Attribute{
|
||||
.name = name,
|
||||
.value = value
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
return Attribute.List{
|
||||
.items = attributes.items
|
||||
};
|
||||
}
|
||||
|
||||
test "self closing tag" {
|
||||
const text =
|
||||
const allocator = std.testing.allocator;
|
||||
var ctx: TestingContext = undefined;
|
||||
ctx.init(allocator,
|
||||
\\ <hello />
|
||||
;
|
||||
);
|
||||
defer ctx.deinit();
|
||||
|
||||
var lexer: Lexer = .{ .text = text };
|
||||
try expectStartTagBegin(&lexer, "hello");
|
||||
try expectStartTagEnd(&lexer);
|
||||
try expectEndTag(&lexer, "hello");
|
||||
try expectDone(&lexer);
|
||||
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);
|
||||
}
|
||||
|
||||
test "tag" {
|
||||
const text =
|
||||
const allocator = std.testing.allocator;
|
||||
var ctx: TestingContext = undefined;
|
||||
ctx.init(allocator,
|
||||
\\ <hello></hello>
|
||||
;
|
||||
);
|
||||
defer ctx.deinit();
|
||||
|
||||
var lexer: Lexer = .{ .text = text };
|
||||
try expectStartTagBegin(&lexer, "hello");
|
||||
try expectStartTagEnd(&lexer);
|
||||
try expectEndTag(&lexer, "hello");
|
||||
try expectDone(&lexer);
|
||||
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);
|
||||
}
|
||||
|
||||
test "tag with prolog" {
|
||||
const text =
|
||||
const allocator = std.testing.allocator;
|
||||
var ctx: TestingContext = undefined;
|
||||
ctx.init(allocator,
|
||||
\\ <?xml version="1.0" encoding="UTF-8"?>
|
||||
\\ <hello></hello>
|
||||
;
|
||||
);
|
||||
defer ctx.deinit();
|
||||
|
||||
var lexer: Lexer = .{ .text = text };
|
||||
try expectStartTagBegin(&lexer, "hello");
|
||||
try expectStartTagEnd(&lexer);
|
||||
try expectEndTag(&lexer, "hello");
|
||||
try expectDone(&lexer);
|
||||
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);
|
||||
}
|
||||
|
||||
test "text content" {
|
||||
const text =
|
||||
const allocator = std.testing.allocator;
|
||||
var ctx: TestingContext = undefined;
|
||||
ctx.init(allocator,
|
||||
\\ <hello> Hello World </hello>
|
||||
;
|
||||
);
|
||||
defer ctx.deinit();
|
||||
|
||||
var lexer: Lexer = .{ .text = text };
|
||||
try expectStartTagBegin(&lexer, "hello");
|
||||
try expectStartTagEnd(&lexer);
|
||||
try expectText(&lexer, "Hello World");
|
||||
try expectEndTag(&lexer, "hello");
|
||||
try expectDone(&lexer);
|
||||
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);
|
||||
}
|
||||
|
||||
test "attributes" {
|
||||
const text =
|
||||
const allocator = std.testing.allocator;
|
||||
var ctx: TestingContext = undefined;
|
||||
ctx.init(allocator,
|
||||
\\ <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>
|
||||
);
|
||||
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);
|
||||
}
|
||||
defer ctx.deinit();
|
||||
|
||||
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);
|
||||
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 {
|
||||
_ = Lexer;
|
||||
_ = Parser;
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
353
src/app.zig
353
src/app.zig
@ -7,7 +7,7 @@ const STBImage = @import("stb_image");
|
||||
const STBTrueType = @import("stb_truetype");
|
||||
const Tiled = @import("tiled");
|
||||
|
||||
const Platform = @import("platform");
|
||||
const Platform = @import("platform/root.zig");
|
||||
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,
|
||||
|
||||
entities_by_layer: std.EnumArray(Entity.Layer, Entity.Id),
|
||||
entity: Entity.Id,
|
||||
|
||||
const empty = Tile{
|
||||
.ground_sprite = null,
|
||||
.entities_by_layer = .initFill(.nil)
|
||||
.entity = .nil
|
||||
};
|
||||
};
|
||||
|
||||
@ -71,17 +71,6 @@ 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);
|
||||
}
|
||||
@ -95,7 +84,6 @@ const Tilemap = struct {
|
||||
|
||||
const Entity = struct {
|
||||
type: Type,
|
||||
layer: Layer,
|
||||
|
||||
pos_x: i32 = -1,
|
||||
pos_y: i32 = -1,
|
||||
@ -108,15 +96,9 @@ 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;
|
||||
};
|
||||
@ -125,45 +107,24 @@ const World = struct {
|
||||
tilemap: Tilemap,
|
||||
entities: Entity.SlotMap,
|
||||
|
||||
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 spawnPlayer(self: *World) !Entity.Id {
|
||||
return try self.entities.insert(Entity{
|
||||
.type = .player,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn deinit(self: *World, gpa: Allocator) void {
|
||||
self.tilemap.deinit(gpa);
|
||||
self.entities.deinit(gpa);
|
||||
fn spawnBox(self: *World, sprite: Gfx.Sprite.Id) !Entity.Id {
|
||||
return try self.entities.insert(Entity{
|
||||
.type = .box,
|
||||
.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 spawnWall(self: *World, sprite: Gfx.Sprite.Id) !Entity.Id {
|
||||
return try self.entities.insert(Entity{
|
||||
.type = .wall,
|
||||
.sprite = sprite
|
||||
});
|
||||
}
|
||||
|
||||
fn getTile(self: *World, x: i32, y: i32) ?*Tile {
|
||||
@ -175,18 +136,18 @@ const World = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
fn setEntityPosition(self: *World, id: Entity.Id, x: i32, y: i32) bool {
|
||||
fn moveEntity(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.entities_by_layer.get(e.layer))) {
|
||||
// Cant't overwrite entity
|
||||
if (self.entities.exists(destination_tile.entity)) {
|
||||
// Cna't overwrite entity
|
||||
return false;
|
||||
}
|
||||
destination_tile.entities_by_layer.set(e.layer, id);
|
||||
destination_tile.entity = id;
|
||||
|
||||
if (self.getTile(e.pos_x, e.pos_y)) |tile| {
|
||||
tile.entities_by_layer.set(e.layer, .nil);
|
||||
tile.entity = .nil;
|
||||
}
|
||||
|
||||
e.pos_x = x;
|
||||
@ -195,37 +156,20 @@ const World = struct {
|
||||
return true;
|
||||
}
|
||||
|
||||
fn moveEntity(self: *World, id: Entity.Id, dx: i32, dy: i32) bool {
|
||||
fn movePlayer(self: *World, id: Entity.Id, dir_x: i32, dir_y: i32) bool {
|
||||
const e = self.entities.get(id) orelse return false;
|
||||
|
||||
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)) {
|
||||
const next_x = e.pos_x+dir_x;
|
||||
const next_y = e.pos_y+dir_y;
|
||||
if (!self.moveEntity(id, next_x, next_y)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self.setEntityPosition(id, next_x, next_y);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
const Level = struct {
|
||||
number: i32,
|
||||
world: World,
|
||||
};
|
||||
|
||||
world: World,
|
||||
current_level: u32 = 0,
|
||||
levels: std.ArrayList(Level),
|
||||
|
||||
roboto_font: Font,
|
||||
tilesheet: Tilesheet,
|
||||
@ -302,170 +246,80 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
||||
.tilesheet = tilesheet,
|
||||
.footstep_buffer = footstep_sound,
|
||||
.sfx_bus = Audio.addBus(.{ .label = "sfx" }),
|
||||
.world = undefined,
|
||||
.levels = .empty,
|
||||
.world = .{
|
||||
.tilemap = undefined,
|
||||
.entities = try .initCapacity(plt.gpa, 256)
|
||||
}
|
||||
};
|
||||
|
||||
self.levels = try self.loadLevels(plt.gpa, plt.assets);
|
||||
self.world = try self.levels.items[0].world.clone(plt.gpa);
|
||||
var player_pos: Vec2 = .zero;
|
||||
{
|
||||
var scratch = std.heap.ArenaAllocator.init(plt.gpa);
|
||||
defer scratch.deinit();
|
||||
|
||||
return null;
|
||||
}
|
||||
var buffers = Tiled.xml.Lexer.Buffers.init(plt.gpa);
|
||||
defer buffers.deinit();
|
||||
|
||||
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
|
||||
);
|
||||
const tilemap = try Tiled.Tilemap.initFromBuffer(plt.gpa, &scratch, &buffers, plt.assets.readFile("tiled/map.tmx"));
|
||||
defer tilemap.deinit();
|
||||
|
||||
var tilesets: Tiled.Tileset.List = .empty;
|
||||
defer tilesets.deinit(gpa);
|
||||
defer tilesets.deinit(plt.gpa);
|
||||
|
||||
try tilesets.add(gpa, "tileset.tsx", try Tiled.Tileset.initFromXml(
|
||||
gpa,
|
||||
try xml_parser.parse(assets.readFile("tiled/tileset.tsx")),
|
||||
.root
|
||||
));
|
||||
try tilesets.add(plt.gpa, "tileset.tsx", try Tiled.Tileset.initFromBuffer(plt.gpa, &scratch, &buffers, plt.assets.readFile("tiled/tileset.tsx")));
|
||||
|
||||
var levels: std.ArrayList(Level) = .empty;
|
||||
const bounds = tilemap.getTileBounds();
|
||||
|
||||
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)
|
||||
}
|
||||
};
|
||||
self.world.tilemap = try Tilemap.init(plt.gpa, bounds.width(), bounds.height());
|
||||
|
||||
const ground = tilemap.getLayer("Ground").?;
|
||||
const foreground = tilemap.getLayer("Foreground").?;
|
||||
const walls = tilemap.getLayer("Walls").?;
|
||||
for (0..bounds.height()) |oy| {
|
||||
for (0..bounds.width()) |ox| {
|
||||
const x = bounds.min_x + @as(i32, @intCast(ox));
|
||||
const y = bounds.min_y + @as(i32, @intCast(oy));
|
||||
const x = bounds.min_x + ox;
|
||||
const y = bounds.min_y + oy;
|
||||
|
||||
const tile = level.world.tilemap.get(ox, oy);
|
||||
const tile = self.world.tilemap.get(ox, oy);
|
||||
|
||||
if (tilemap.getTile(ground, tilesets, x, y)) |tiled_tile| {
|
||||
const pos = tiled_tile.getPosition();
|
||||
tile.ground_sprite = self.tilesheet.get(pos.x, pos.y);
|
||||
tile.ground_sprite = tilesheet.get(pos.x, pos.y);
|
||||
}
|
||||
|
||||
if (tilemap.getTile(foreground, tilesets, x, y)) |tiled_tile| {
|
||||
if (tilemap.getTile(walls, tilesets, x, y)) |tiled_tile| {
|
||||
const pos = tiled_tile.getPosition();
|
||||
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 wall_id = try self.world.spawnWall(tilesheet.get(pos.x, pos.y));
|
||||
_ = self.world.moveEntity(wall_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 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (tilemap.getLayer("Markers").?.variant.object.items) |obj| {
|
||||
if (obj.shape != .point) {
|
||||
continue;
|
||||
}
|
||||
if (!std.mem.eql(u8, obj.name, "spawn")) {
|
||||
continue;
|
||||
}
|
||||
const player_id = try self.world.spawnPlayer();
|
||||
_ = self.world.moveEntity(player_id, @intFromFloat(player_pos.x), @intFromFloat(player_pos.y));
|
||||
|
||||
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
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn frame(self: *App, plt: Platform.Frame) !void {
|
||||
@ -500,14 +354,21 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
||||
canvas.push();
|
||||
defer canvas.pop(clear_color);
|
||||
|
||||
var goal_count: u32 = 0;
|
||||
var box_at_goal_count: u32 = 0;
|
||||
{
|
||||
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 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.moveEntity(entity_id, dir_x, dir_y)) {
|
||||
if ((dir_x != 0 or dir_y != 0) and self.world.movePlayer(entity_id, dir_x, dir_y)) {
|
||||
var new_direction = entity.player_direction;
|
||||
if (dir_x < 0) {
|
||||
new_direction = .left;
|
||||
@ -542,38 +403,9 @@ 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(
|
||||
@ -584,23 +416,18 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
// self.roboto_font.drawText(.{
|
||||
// .text = try std.fmt.allocPrint(plt.frame, "Hello {:.0}!", .{plt.time()}),
|
||||
// .pos = .init(0, 0),
|
||||
// .height = 32,
|
||||
// });
|
||||
}
|
||||
|
||||
pub fn deinit(self: *App, plt: Platform.Deinit) void {
|
||||
self.roboto_font.deinit(plt.gpa);
|
||||
Gfx.deinitSpritesheet(self.roboto_font.spritesheet);
|
||||
|
||||
self.world.deinit(plt.gpa);
|
||||
|
||||
for (self.levels.items) |*level| {
|
||||
level.world.deinit(plt.gpa);
|
||||
}
|
||||
self.levels.deinit(plt.gpa);
|
||||
self.world.tilemap.deinit(plt.gpa);
|
||||
self.world.entities.slots.deinit(plt.gpa);
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
const Platform = @import("platform");
|
||||
const Math = @import("math");
|
||||
const Vec2 = Math.Vec2;
|
||||
|
||||
const Platform = @import("platform/root.zig");
|
||||
const Gfx = Platform.Gfx;
|
||||
const Color = Platform.Color;
|
||||
|
||||
const Vec2 = Platform.Math.Vec2;
|
||||
|
||||
const Canvas = @This();
|
||||
|
||||
size: Vec2,
|
||||
|
||||
54
src/font.zig
54
src/font.zig
@ -4,7 +4,7 @@ const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Io = std.Io;
|
||||
|
||||
const Platform = @import("platform");
|
||||
const Platform = @import("platform/root.zig");
|
||||
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.Image{
|
||||
const bitmap = Gfx.ImageData{
|
||||
.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.insertOrGet(key).?.id;
|
||||
const id2 = cache.insertOrGet(key).?.id;
|
||||
const id1 = cache.insert(key).?;
|
||||
const id2 = cache.insert(key).?;
|
||||
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.insertOrGet(key).?.id;
|
||||
const id1 = cache.insert(key).?;
|
||||
cache.remove(id1);
|
||||
const id2 = cache.insertOrGet(key).?.id;
|
||||
const id2 = cache.insert(key).?;
|
||||
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.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;
|
||||
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) }).?;
|
||||
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.insertOrGet(Glyph.Key{ .index = @intCast(i), .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
|
||||
const id = cache.insert(Glyph.Key{ .index = @intCast(i), .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
|
||||
try std.testing.expect(id != null);
|
||||
}
|
||||
const id = cache.insertOrGet(Glyph.Key{ .index = limit, .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
|
||||
const id = cache.insert(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.insertOrGet(Glyph.Key{ .index = @intCast(i), .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
|
||||
const id = cache.insert(Glyph.Key{ .index = @intCast(i), .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
|
||||
try std.testing.expect(id != null);
|
||||
try ids.append(gpa, id.?.id);
|
||||
try ids.append(gpa, 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.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 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 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.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 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 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.insertOrGet(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
|
||||
const id = cache.insert(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
|
||||
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.insertOrGet(key1).?.id;
|
||||
const id1 = cache.insert(key1).?;
|
||||
const key2 = Glyph.Key{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
|
||||
const id2 = cache.insertOrGet(key2).?.id;
|
||||
const id2 = cache.insert(key2).?;
|
||||
const key3 = Glyph.Key{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
|
||||
const id3 = cache.insertOrGet(key3).?.id;
|
||||
const id3 = cache.insert(key3).?;
|
||||
|
||||
_ = cache.insertOrGet(key2);
|
||||
_ = cache.lookup(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.insertOrGet(key1);
|
||||
_ = cache.lookup(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.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 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 lru = try cache.getLRUItems(gpa);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Platform = @import("platform");
|
||||
const Platform = @import("platform/root.zig");
|
||||
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());
|
||||
_ = Platform;
|
||||
_ = @import("./platform/slot_map.zig");
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ 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");
|
||||
@ -125,7 +126,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, .dynamic_update = true },
|
||||
.usage = .{ .vertex_buffer = true, .stream_update = true },
|
||||
.label = "quad-vertices"
|
||||
});
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ const std = @import("std");
|
||||
const log = std.log.scoped(.app);
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const Platform = @import("platform");
|
||||
const Platform = @import("platform/root.zig");
|
||||
const Gfx = Platform.Gfx;
|
||||
const Vec2 = Platform.Vec2;
|
||||
const Rect = Platform.Rect;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user