Compare commits
9 Commits
0ac0cd0c37
...
7632329c92
| Author | SHA1 | Date | |
|---|---|---|---|
| 7632329c92 | |||
| 1165c53683 | |||
| 6f21915c9a | |||
| a6bf95c570 | |||
| b6d9f1664b | |||
| 96cfcd4626 | |||
| e0d0f479be | |||
| 53e058ad74 | |||
| f7e6c25a82 |
Binary file not shown.
|
Before Width: | Height: | Size: 171 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 153 KiB |
@ -1,81 +1,104 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<map version="1.10" tiledversion="1.12.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="64" tileheight="64" infinite="0" nextlayerid="6" nextobjectid="4">
|
<map version="1.10" tiledversion="1.12.2" orientation="orthogonal" renderorder="right-down" width="30" height="20" tilewidth="64" tileheight="64" infinite="1" nextlayerid="8" nextobjectid="19">
|
||||||
<tileset firstgid="1" source="tileset.tsx"/>
|
<tileset firstgid="1" source="tileset.tsx"/>
|
||||||
<layer id="2" name="Ground" width="30" height="20">
|
<layer id="2" name="Ground" width="30" height="20">
|
||||||
<data encoding="csv">
|
<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,
|
<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,0,0,0,0,0,0,
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,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,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,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,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,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,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,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,
|
</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,
|
<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,
|
||||||
</data>
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,90,90,90,90,90,90,90,0,0,0,0,0,
|
||||||
|
0,0,0,0,90,90,90,90,90,90,90,0,0,0,0,0,
|
||||||
|
0,0,0,0,90,90,90,90,90,90,90,0,0,0,0,0,
|
||||||
|
0,0,0,0,90,90,90,90,90,90,90,0,0,0,0,0,
|
||||||
|
0,0,0,0,90,90,90,90,90,90,90,0,0,0,0,0,
|
||||||
|
0,0,0,0,90,90,90,90,90,90,90,0,0,0,0,0,
|
||||||
|
0,0,0,0,90,90,90,90,90,90,90,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
|
||||||
|
</chunk>
|
||||||
|
</data>
|
||||||
</layer>
|
</layer>
|
||||||
<layer id="1" name="Walls" width="30" height="20">
|
<layer id="1" name="Foreground" width="30" height="20">
|
||||||
<data encoding="csv">
|
<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,
|
<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,0,0,0,0,0,0,
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,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,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,98,101,98,98,98,98,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,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,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,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,7,0,0,26,0,98,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,0,0,0,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,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,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,
|
</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,
|
<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,
|
||||||
</data>
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
</layer>
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
<layer id="4" name="Boxes" width="30" height="20">
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
||||||
<data encoding="csv">
|
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,98,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,0,0,0,0,0,0,0,0,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,0,0,0,0,0,0,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,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,0,0,0,0,0,0,0,0,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,0,0,0,0,0,0,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,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,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,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,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,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,
|
</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,
|
</data>
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
||||||
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
||||||
0,0,0,0,0,0,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>
|
||||||
<objectgroup id="5" name="Markers">
|
<objectgroup id="5" name="Markers">
|
||||||
<object id="1" name="spawn" x="479" y="550">
|
<object id="1" name="spawn" x="484" y="555">
|
||||||
|
<point/>
|
||||||
|
</object>
|
||||||
|
<object id="17" name="spawn" x="1381.33" y="497.333">
|
||||||
<point/>
|
<point/>
|
||||||
</object>
|
</object>
|
||||||
</objectgroup>
|
</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>
|
</map>
|
||||||
|
|||||||
@ -13,7 +13,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tileset.tsx": {
|
"tileset.tsx": {
|
||||||
"scaleInDock": 1
|
"scaleInDock": 0.5,
|
||||||
|
"scaleInEditor": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"last.imagePath": "/home/rokas/code/games/sokol-template-v2/assets/sokoban",
|
"last.imagePath": "/home/rokas/code/games/sokol-template-v2/assets/sokoban",
|
||||||
@ -24,7 +25,9 @@
|
|||||||
"map.tmx"
|
"map.tmx"
|
||||||
],
|
],
|
||||||
"project": "sokoban.tiled-project",
|
"project": "sokoban.tiled-project",
|
||||||
|
"property.type": "int",
|
||||||
"recentFiles": [
|
"recentFiles": [
|
||||||
|
"tileset.tsx",
|
||||||
"map.tmx"
|
"map.tmx"
|
||||||
],
|
],
|
||||||
"tileset.lastUsedFormat": "tsx",
|
"tileset.lastUsedFormat": "tsx",
|
||||||
|
|||||||
@ -1,4 +1,74 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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">
|
<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"/>
|
<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>
|
</tileset>
|
||||||
|
|||||||
165
build.zig
165
build.zig
@ -1,10 +1,13 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const Build = std.Build;
|
||||||
const sokol = @import("sokol");
|
const sokol = @import("sokol");
|
||||||
|
|
||||||
pub fn build(b: *std.Build) !void {
|
pub fn build(b: *std.Build) !void {
|
||||||
const target = b.standardTargetOptions(.{});
|
const target = b.standardTargetOptions(.{});
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const name = "sokol_template_v2";
|
||||||
|
|
||||||
const assets_dir = "assets";
|
const assets_dir = "assets";
|
||||||
const asset_files = [_][]const u8{
|
const asset_files = [_][]const u8{
|
||||||
"sokoban/sokoban_tilesheet.png",
|
"sokoban/sokoban_tilesheet.png",
|
||||||
@ -34,101 +37,107 @@ pub fn build(b: *std.Build) !void {
|
|||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
const build_options = b.addOptions();
|
|
||||||
build_options.addOption(bool, "has_imgui", has_imgui);
|
|
||||||
build_options.addOption(?[]const u8, "assets_dir", if (assets_from_filesystem) asset_dir_realpath else null);
|
|
||||||
exe_mod.addOptions("build_options", build_options);
|
|
||||||
|
|
||||||
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")
|
|
||||||
});
|
|
||||||
exe_mod.addImport("math", math_mod);
|
|
||||||
|
|
||||||
const dep_tiled = b.dependency("tiled", .{});
|
const dep_tiled = b.dependency("tiled", .{});
|
||||||
exe_mod.addImport("tiled", dep_tiled.module("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");
|
|
||||||
exe_mod.linkLibrary(dep_sokol.artifact("sokol_clib"));
|
|
||||||
exe_mod.addImport("sokol", mod_sokol);
|
|
||||||
|
|
||||||
const dep_shdc = dep_sokol.builder.dependency("shdc", .{});
|
|
||||||
|
|
||||||
var slang = sokol.shdc.Slang{};
|
|
||||||
if (is_wasm) {
|
|
||||||
slang.wgsl = true;
|
|
||||||
} else {
|
|
||||||
slang.glsl410 = true;
|
|
||||||
slang.hlsl4 = true;
|
|
||||||
slang.metal_macos = true;
|
|
||||||
slang.glsl300es = true;
|
|
||||||
}
|
|
||||||
const mod_shader = try sokol.shdc.createModule(b, "shader", mod_sokol, .{
|
|
||||||
.shdc_dep = dep_shdc,
|
|
||||||
.input = "src/platform/shader.glsl",
|
|
||||||
.output = "src/platform/shader.zig",
|
|
||||||
.slang = slang
|
|
||||||
});
|
|
||||||
mod_shader.addImport("math", math_mod);
|
|
||||||
exe_mod.addImport("shader", mod_shader);
|
|
||||||
|
|
||||||
if (has_imgui) {
|
|
||||||
if (b.lazyDependency("cimgui", .{
|
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
})) |dep_cimgui| {
|
|
||||||
const cimgui = b.lazyImport(@This(), "cimgui").?;
|
|
||||||
const cimgui_conf = cimgui.getConfig(true);
|
|
||||||
exe_mod.addImport("cimgui", dep_cimgui.module(cimgui_conf.module_name));
|
|
||||||
dep_sokol.artifact("sokol_clib").root_module.addIncludePath(dep_cimgui.path(cimgui_conf.include_dir));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const dep_stb = b.dependency("stb", .{});
|
const dep_stb = b.dependency("stb", .{});
|
||||||
exe_mod.addImport("stb_image", dep_stb.module("stb_image"));
|
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"));
|
exe_mod.addImport("stb_truetype", dep_stb.module("stb_truetype"));
|
||||||
|
|
||||||
const dep_tracy = b.dependency("tracy", .{
|
var dep_sokol: *Build.Dependency = undefined;
|
||||||
.target = target,
|
const platform_mod = b.createModule(.{
|
||||||
.optimize = optimize,
|
.root_source_file = b.path("src/platform/root.zig")
|
||||||
.tracy_enable = has_tracy,
|
|
||||||
.tracy_only_localhost = true
|
|
||||||
});
|
});
|
||||||
if (has_tracy) {
|
exe_mod.addImport("platform", platform_mod);
|
||||||
exe_mod.linkLibrary(dep_tracy.artifact("tracy"));
|
{
|
||||||
|
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);
|
||||||
|
|
||||||
|
platform_mod.addImport("embedded_assets", try createEmbeddedAssets(b, assets_dir, &asset_files));
|
||||||
|
|
||||||
|
const math_mod = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/platform/math.zig")
|
||||||
|
});
|
||||||
|
platform_mod.addImport("math", math_mod);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
const dep_shdc = dep_sokol.builder.dependency("shdc", .{});
|
||||||
|
|
||||||
|
var slang = sokol.shdc.Slang{};
|
||||||
|
if (is_wasm) {
|
||||||
|
slang.wgsl = true;
|
||||||
|
} else {
|
||||||
|
slang.glsl410 = true;
|
||||||
|
slang.hlsl4 = true;
|
||||||
|
slang.metal_macos = true;
|
||||||
|
slang.glsl300es = true;
|
||||||
|
}
|
||||||
|
const mod_shader = try sokol.shdc.createModule(b, "shader", mod_sokol, .{
|
||||||
|
.shdc_dep = dep_shdc,
|
||||||
|
.input = "src/platform/shader.glsl",
|
||||||
|
.output = "src/platform/shader.zig",
|
||||||
|
.slang = slang
|
||||||
|
});
|
||||||
|
mod_shader.addImport("math", math_mod);
|
||||||
|
platform_mod.addImport("shader", mod_shader);
|
||||||
|
|
||||||
|
if (has_imgui) {
|
||||||
|
if (b.lazyDependency("cimgui", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
})) |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));
|
||||||
|
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_tracy = b.dependency("tracy", .{
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.tracy_enable = has_tracy,
|
||||||
|
.tracy_only_localhost = true
|
||||||
|
});
|
||||||
|
if (has_tracy) {
|
||||||
|
platform_mod.linkLibrary(dep_tracy.artifact("tracy"));
|
||||||
|
}
|
||||||
|
platform_mod.addImport("tracy", dep_tracy.module("tracy"));
|
||||||
}
|
}
|
||||||
exe_mod.addImport("tracy", dep_tracy.module("tracy"));
|
|
||||||
|
|
||||||
var run_cmd_step: *std.Build.Step = undefined;
|
var run_cmd_step: *std.Build.Step = undefined;
|
||||||
if (is_wasm) {
|
if (is_wasm) {
|
||||||
if (true) {
|
@panic("TODO: Test wasm support on 0.17");
|
||||||
@panic("TODO: test in 0.17");
|
|
||||||
}
|
|
||||||
|
|
||||||
const build_step = buildWasm(b, .{
|
// const build_step = buildWasm(b, .{
|
||||||
.name = "index",
|
// .name = "index",
|
||||||
.root_module = exe_mod,
|
// .root_module = exe_mod,
|
||||||
.dep_sokol = dep_sokol,
|
// .dep_sokol = dep_sokol,
|
||||||
});
|
// });
|
||||||
b.getInstallStep().dependOn(build_step);
|
// b.getInstallStep().dependOn(build_step);
|
||||||
|
//
|
||||||
const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{});
|
// const dep_emsdk = dep_sokol.builder.dependency("emsdk", .{});
|
||||||
const emrun_step = sokol.emRunStep(b, .{ .name = "index", .emsdk = dep_emsdk });
|
// const emrun_step = sokol.emRunStep(b, .{ .name = "index", .emsdk = dep_emsdk });
|
||||||
emrun_step.step.dependOn(build_step);
|
// emrun_step.step.dependOn(build_step);
|
||||||
run_cmd_step = &emrun_step.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
|
// TODO: Create a zip archive of all of the files. Would be useful for easier itch.io upload
|
||||||
} else {
|
} else {
|
||||||
const exe = buildNative(b, .{
|
const exe = buildNative(b, .{
|
||||||
.name = "sokol_template_v2",
|
.name = name,
|
||||||
.root_module = exe_mod
|
.root_module = exe_mod
|
||||||
});
|
});
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
|||||||
@ -2,11 +2,15 @@
|
|||||||
.name = .sokol_template_v2,
|
.name = .sokol_template_v2,
|
||||||
.version = "0.0.0",
|
.version = "0.0.0",
|
||||||
.fingerprint = 0xf440b4e6e51f5206, // Changing this has security and trust implications.
|
.fingerprint = 0xf440b4e6e51f5206, // Changing this has security and trust implications.
|
||||||
.minimum_zig_version = "0.16.0",
|
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.sokol = .{
|
.sokol = .{
|
||||||
.url = "git+https://github.com/floooh/sokol-zig.git#ba97ac4e82e36a0f6c62f7fad5dc9c2d82beadd8",
|
.url = "git+https://github.com/floooh/sokol-zig.git#ff40f04d4f51c73b2bf460d87fd2d7aa0dedc22f",
|
||||||
.hash = "sokol-0.1.0-pb1HK4prNwDGx2RJFyv7VtYJubDahi19XKioPzPeMCxv",
|
.hash = "sokol-0.1.0-pb1HKygbQQCINV-qSe4SFm92QWaT_95VpWTJkwYqzOrX",
|
||||||
|
},
|
||||||
|
.cimgui = .{
|
||||||
|
.url = "git+https://github.com/floooh/dcimgui.git#e2f0e0d93adec02743c55940be23ffe286e857f7",
|
||||||
|
.hash = "cimgui-0.1.0-44ClkR8dmgDqqnfloyLE1bUAtl4zSKTT1wxaHSEC0ikV",
|
||||||
|
.lazy = true
|
||||||
},
|
},
|
||||||
.stb = .{
|
.stb = .{
|
||||||
.path = "./libs/stb",
|
.path = "./libs/stb",
|
||||||
@ -17,11 +21,6 @@
|
|||||||
.tracy = .{
|
.tracy = .{
|
||||||
.path = "./libs/tracy",
|
.path = "./libs/tracy",
|
||||||
},
|
},
|
||||||
.cimgui = .{
|
|
||||||
.url = "git+https://github.com/floooh/dcimgui.git#3c2b41a9333b1195e2c2f8ce6a2d3e50660a73bf",
|
|
||||||
.hash = "cimgui-0.1.0-44ClkR8dmgDqqnfloyLE1bUAtl4zSKTT1wxaHSEC0ikV",
|
|
||||||
.lazy = true
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"build.zig",
|
"build.zig",
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
const Property = @import("./property.zig");
|
const Property = @import("./property.zig");
|
||||||
const xml = @import("./xml.zig");
|
const xml = @import("./xml.zig");
|
||||||
@ -11,41 +12,104 @@ const Layer = @This();
|
|||||||
pub const TileVariant = struct {
|
pub const TileVariant = struct {
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
data: []u32,
|
chunks: []Chunk,
|
||||||
|
|
||||||
fn initDataFromXml(
|
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);
|
||||||
|
|
||||||
|
var split_iter = std.mem.splitScalar(u8, csv, ',');
|
||||||
|
while (split_iter.next()) |raw_tile_id| {
|
||||||
|
const tile_id_str = std.mem.trim(u8, raw_tile_id, &std.ascii.whitespace);
|
||||||
|
const tile_id = try std.fmt.parseInt(u32, tile_id_str, 10);
|
||||||
|
cells.appendAssumeCapacity(tile_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cells.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initFromXml(
|
||||||
arena: std.mem.Allocator,
|
arena: std.mem.Allocator,
|
||||||
scratch: *std.heap.ArenaAllocator,
|
doc: xml.Document,
|
||||||
lexer: *xml.Lexer,
|
element_id: xml.Element.Id,
|
||||||
) ![]u32 {
|
) !TileVariant {
|
||||||
var iter = xml.TagParser.init(lexer);
|
const tag = try doc.getTagExpect(element_id, "layer");
|
||||||
const attrs = try iter.begin("data");
|
const attrs = tag.variant.tag.attributes;
|
||||||
|
|
||||||
const encoding = attrs.get("encoding") orelse "csv";
|
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
|
// TODO: compression
|
||||||
|
|
||||||
var temp_tiles: std.ArrayList(u32) = .empty;
|
var chunks: std.ArrayList(Chunk) = .empty;
|
||||||
|
|
||||||
if (std.mem.eql(u8, encoding, "csv")) {
|
if (std.mem.eql(u8, encoding, "csv")) {
|
||||||
const text = try lexer.nextExpectText();
|
try chunks.ensureTotalCapacityPrecise(arena, doc.countChildren(data_id));
|
||||||
var split_iter = std.mem.splitScalar(u8, text, ',');
|
|
||||||
while (split_iter.next()) |raw_tile_id| {
|
var iter = doc.iterChildren(data_id);
|
||||||
const tile_id_str = std.mem.trim(u8, raw_tile_id, &std.ascii.whitespace);
|
while (iter.next()) |child_id| {
|
||||||
const tile_id = try std.fmt.parseInt(u32, tile_id_str, 10);
|
const child = doc.get(child_id).?;
|
||||||
try temp_tiles.append(scratch.allocator(), tile_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 {
|
} else {
|
||||||
return error.UnknownEncodingType;
|
return error.UnknownEncodingType;
|
||||||
}
|
}
|
||||||
|
|
||||||
try iter.finish("data");
|
return TileVariant{
|
||||||
|
.width = layer_width,
|
||||||
return try arena.dupe(u32, temp_tiles.items);
|
.height = layer_height,
|
||||||
|
.chunks = chunks.items
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(self: TileVariant, x: usize, y: usize) ?u32 {
|
pub fn get(self: TileVariant, x: i32, y: i32) ?u32 {
|
||||||
if ((0 <= x and x < self.width) and (0 <= y and y < self.height)) {
|
for (self.chunks) |chunk| {
|
||||||
return self.data[y * self.width + x];
|
const ox = x - chunk.x;
|
||||||
|
const oy = y - chunk.y;
|
||||||
|
|
||||||
|
if ((0 <= ox and ox < chunk.width) and (0 <= oy and oy < chunk.height)) {
|
||||||
|
return chunk.data[@as(u32, @intCast(oy)) * chunk.width + @as(u32, @intCast(ox))];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -59,9 +123,9 @@ pub const ImageVariant = struct {
|
|||||||
width: ?u32,
|
width: ?u32,
|
||||||
height: ?u32,
|
height: ?u32,
|
||||||
|
|
||||||
fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Image {
|
fn initFromXml(arena: std.mem.Allocator, doc: xml.Document, element_id: xml.Element.Id) !Image {
|
||||||
var iter = xml.TagParser.init(lexer);
|
const tag = try doc.getTagExpect(element_id, "image");
|
||||||
const attrs = try iter.begin("image");
|
const attrs = tag.variant.tag.attributes;
|
||||||
|
|
||||||
// TODO: format
|
// TODO: format
|
||||||
const source = try attrs.getDupe(arena, "source") orelse return error.MissingSource;
|
const source = try attrs.getDupe(arena, "source") orelse return error.MissingSource;
|
||||||
@ -69,8 +133,6 @@ pub const ImageVariant = struct {
|
|||||||
const height = try attrs.getNumber(u32, "height") orelse null;
|
const height = try attrs.getNumber(u32, "height") orelse null;
|
||||||
const transparent_color = try attrs.getColor("trans", false);
|
const transparent_color = try attrs.getColor("trans", false);
|
||||||
|
|
||||||
try iter.finish("image");
|
|
||||||
|
|
||||||
return Image{
|
return Image{
|
||||||
.source = source,
|
.source = source,
|
||||||
.transparent_color = transparent_color,
|
.transparent_color = transparent_color,
|
||||||
@ -126,15 +188,6 @@ pub const Type = enum {
|
|||||||
.{ "imagelayer", .image },
|
.{ "imagelayer", .image },
|
||||||
.{ "group", .group }
|
.{ "group", .group }
|
||||||
});
|
});
|
||||||
|
|
||||||
fn toXmlName(self: Type) []const u8 {
|
|
||||||
return switch (self) {
|
|
||||||
.tile => "layer",
|
|
||||||
.object => "objectgroup",
|
|
||||||
.image => "imagelayer",
|
|
||||||
.group => "group",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Variant = union(Type) {
|
pub const Variant = union(Type) {
|
||||||
@ -159,16 +212,13 @@ variant: Variant,
|
|||||||
|
|
||||||
pub fn initFromXml(
|
pub fn initFromXml(
|
||||||
arena: std.mem.Allocator,
|
arena: std.mem.Allocator,
|
||||||
scratch: *std.heap.ArenaAllocator,
|
doc: xml.Document,
|
||||||
lexer: *xml.Lexer,
|
element_id: xml.Element.Id
|
||||||
) !Layer {
|
) !Layer {
|
||||||
const value = try lexer.peek() orelse return error.MissingStartTag;
|
const tag = doc.getTag(element_id) orelse return error.TagNotFound;
|
||||||
if (value != .start_tag) return error.MissingStartTag;
|
|
||||||
|
|
||||||
var layer_type = Type.name_map.get(value.start_tag.name) orelse return error.UnknownLayerType;
|
const layer_type = Type.name_map.get(tag.variant.tag.name) orelse return error.UnknownLayerType;
|
||||||
|
const attrs = tag.variant.tag.attributes;
|
||||||
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 id = try attrs.getNumber(u32, "id") orelse return error.MissingId;
|
||||||
const name = try attrs.getDupe(arena, "name") orelse "";
|
const name = try attrs.getDupe(arena, "name") orelse "";
|
||||||
@ -184,14 +234,12 @@ pub fn initFromXml(
|
|||||||
var variant: Variant = undefined;
|
var variant: Variant = undefined;
|
||||||
switch (layer_type) {
|
switch (layer_type) {
|
||||||
.tile => {
|
.tile => {
|
||||||
const width = try attrs.getNumber(u32, "width") orelse return error.MissingWidth;
|
variant = Variant{
|
||||||
const height = try attrs.getNumber(u32, "height") orelse return error.MissingHeight;
|
.tile = try TileVariant.initFromXml(
|
||||||
variant = .{
|
arena,
|
||||||
.tile = TileVariant{
|
doc,
|
||||||
.width = width,
|
element_id
|
||||||
.height = height,
|
)
|
||||||
.data = &[0]u32{}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
.image => {
|
.image => {
|
||||||
@ -230,45 +278,41 @@ pub fn initFromXml(
|
|||||||
var objects: std.ArrayList(Object) = .empty;
|
var objects: std.ArrayList(Object) = .empty;
|
||||||
var layers: std.ArrayList(Layer) = .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) {
|
if (variant == .object) {
|
||||||
variant.object.items = try arena.dupe(Object, objects.items);
|
try objects.ensureTotalCapacityPrecise(arena, doc.countChildren(element_id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (variant == .group) {
|
if (variant == .group) {
|
||||||
variant.group.layers = try arena.dupe(Layer, layers.items);
|
try layers.ensureTotalCapacityPrecise(arena, doc.countChildren(element_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
var iter = doc.iterChildren(element_id);
|
||||||
|
while (iter.next()) |child_id| {
|
||||||
|
const child = doc.getTag(child_id) orelse continue;
|
||||||
|
const child_name = child.variant.tag.name;
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, child_name, "properties")) {
|
||||||
|
properties = try Property.List.initFromXml(arena, doc, child_id);
|
||||||
|
|
||||||
|
} else if (variant == .image and std.mem.eql(u8, child_name, "image")) {
|
||||||
|
variant.image.image = try ImageVariant.Image.initFromXml(arena, doc, child_id);
|
||||||
|
|
||||||
|
} else if (variant == .object and std.mem.eql(u8, child_name, "object")) {
|
||||||
|
const object = try Object.initFromXml(arena, doc, child_id);
|
||||||
|
objects.appendAssumeCapacity(object);
|
||||||
|
|
||||||
|
} else if (variant == .group and Layer.Type.name_map.has(child_name)) {
|
||||||
|
const layer = try initFromXml(arena, doc, child_id);
|
||||||
|
layers.appendAssumeCapacity(layer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant == .object) {
|
||||||
|
variant.object.items = objects.items;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (variant == .group) {
|
||||||
|
variant.group.layers = layers.items;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Layer{
|
return Layer{
|
||||||
@ -286,7 +330,3 @@ pub fn initFromXml(
|
|||||||
.variant = variant,
|
.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,6 +3,7 @@ const std = @import("std");
|
|||||||
const xml = @import("./xml.zig");
|
const xml = @import("./xml.zig");
|
||||||
const Position = @import("./position.zig");
|
const Position = @import("./position.zig");
|
||||||
const Color = @import("./color.zig");
|
const Color = @import("./color.zig");
|
||||||
|
const Property = @import("./property.zig");
|
||||||
|
|
||||||
const Object = @This();
|
const Object = @This();
|
||||||
|
|
||||||
@ -113,14 +114,15 @@ class: []const u8,
|
|||||||
rotation: f32, // TODO: maybe this field should be moved to Shape struct
|
rotation: f32, // TODO: maybe this field should be moved to Shape struct
|
||||||
visible: bool,
|
visible: bool,
|
||||||
shape: Shape,
|
shape: Shape,
|
||||||
|
properties: Property.List,
|
||||||
|
|
||||||
pub fn initFromXml(
|
pub fn initFromXml(
|
||||||
arena: std.mem.Allocator,
|
arena: std.mem.Allocator,
|
||||||
scratch: *std.heap.ArenaAllocator,
|
doc: xml.Document,
|
||||||
lexer: *xml.Lexer,
|
element_id: xml.Element.Id
|
||||||
) !Object {
|
) !Object {
|
||||||
var iter = xml.TagParser.init(lexer);
|
const tag = try doc.getTagExpect(element_id, "object");
|
||||||
const attrs = try iter.begin("object");
|
const attrs = tag.variant.tag.attributes;
|
||||||
|
|
||||||
const id = try attrs.getNumber(u32, "id") orelse return error.MissingId;
|
const id = try attrs.getNumber(u32, "id") orelse return error.MissingId;
|
||||||
const name = try attrs.getDupe(arena, "name") orelse "";
|
const name = try attrs.getDupe(arena, "name") orelse "";
|
||||||
@ -134,9 +136,14 @@ pub fn initFromXml(
|
|||||||
|
|
||||||
var shape: ?Shape = null;
|
var shape: ?Shape = null;
|
||||||
|
|
||||||
while (try iter.next()) |node| {
|
var iter = doc.iterChildren(element_id);
|
||||||
|
while (iter.next()) |child_id| {
|
||||||
|
const child = doc.getTag(child_id) orelse continue;
|
||||||
|
const child_name = child.variant.tag.name;
|
||||||
|
const child_attrs = child.variant.tag.attributes;
|
||||||
|
|
||||||
if (shape == null) {
|
if (shape == null) {
|
||||||
if (node.isTag("point")) {
|
if (std.mem.eql(u8, child_name, "point")) {
|
||||||
shape = .{
|
shape = .{
|
||||||
.point = Shape.Point{
|
.point = Shape.Point{
|
||||||
.x = x,
|
.x = x,
|
||||||
@ -144,7 +151,7 @@ pub fn initFromXml(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} else if (node.isTag("ellipse")) {
|
} else if (std.mem.eql(u8, child_name, "ellipse")) {
|
||||||
shape = .{
|
shape = .{
|
||||||
.ellipse = Shape.Ellipse{
|
.ellipse = Shape.Ellipse{
|
||||||
.x = x,
|
.x = x,
|
||||||
@ -154,11 +161,11 @@ pub fn initFromXml(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} else if (node.isTag("text")) {
|
} else if (std.mem.eql(u8, child_name, "text")) {
|
||||||
const HorizontalShape = Shape.Text.HorizontalAlign;
|
const HorizontalShape = Shape.Text.HorizontalAlign;
|
||||||
const VerticalAlign = Shape.Text.VerticalAlign;
|
const VerticalAlign = Shape.Text.VerticalAlign;
|
||||||
|
|
||||||
const text_attrs = node.tag.attributes;
|
const text_attrs = child_attrs;
|
||||||
const word_wrap = try text_attrs.getBool("wrap", "1", "0") orelse false;
|
const word_wrap = try text_attrs.getBool("wrap", "1", "0") orelse false;
|
||||||
const color = try text_attrs.getColor("color", true) orelse Color.black;
|
const color = try text_attrs.getColor("color", true) orelse Color.black;
|
||||||
const horizontal_align = try text_attrs.getEnum(HorizontalShape, "halign", HorizontalShape.map) orelse .left;
|
const horizontal_align = try text_attrs.getEnum(HorizontalShape, "halign", HorizontalShape.map) orelse .left;
|
||||||
@ -171,13 +178,13 @@ pub fn initFromXml(
|
|||||||
const pixel_size = try text_attrs.getNumber(f32, "pixelsize") orelse 16;
|
const pixel_size = try text_attrs.getNumber(f32, "pixelsize") orelse 16;
|
||||||
const font_family = try text_attrs.getDupe(arena, "fontfamily") orelse "sans-serif";
|
const font_family = try text_attrs.getDupe(arena, "fontfamily") orelse "sans-serif";
|
||||||
|
|
||||||
_ = try lexer.nextExpectStartTag("text");
|
|
||||||
var content: []const u8 = "";
|
var content: []const u8 = "";
|
||||||
const content_value = try lexer.peek();
|
const text_element = try doc.getTagExpect(child.tree.first_child, "text");
|
||||||
if (content_value != null and content_value.? == .text) {
|
if (doc.get(text_element.tree.first_child)) |content_element| {
|
||||||
content = try arena.dupe(u8, content_value.?.text);
|
if (content_element.variant == .text) {
|
||||||
|
content = try arena.dupe(u8, content_element.variant.text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
try lexer.skipUntilMatchingEndTag("text");
|
|
||||||
|
|
||||||
shape = .{
|
shape = .{
|
||||||
.text = Shape.Text{
|
.text = Shape.Text{
|
||||||
@ -203,14 +210,16 @@ pub fn initFromXml(
|
|||||||
};
|
};
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
} else if (node.isTag("polygon")) {
|
} else if (std.mem.eql(u8, child_name, "polygon")) {
|
||||||
const points_str = node.tag.attributes.get("points") orelse "";
|
const points_str = child_attrs.get("points") orelse "";
|
||||||
|
|
||||||
var points: std.ArrayList(Position) = .empty;
|
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, ' ');
|
var point_iter = std.mem.splitScalar(u8, points_str, ' ');
|
||||||
while (point_iter.next()) |point_str| {
|
while (point_iter.next()) |point_str| {
|
||||||
const point = try Position.parseCommaDelimited(point_str);
|
const point = try Position.parseCommaDelimited(point_str);
|
||||||
try points.append(scratch.allocator(), point);
|
points.appendAssumeCapacity(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
shape = .{
|
shape = .{
|
||||||
@ -223,7 +232,6 @@ pub fn initFromXml(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try iter.skip();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shape == null) {
|
if (shape == null) {
|
||||||
@ -249,7 +257,7 @@ pub fn initFromXml(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try iter.finish("object");
|
const properties = try Property.List.initFromXmlContainer(arena, doc, element_id);
|
||||||
|
|
||||||
return Object{
|
return Object{
|
||||||
.id = id,
|
.id = id,
|
||||||
@ -257,7 +265,8 @@ pub fn initFromXml(
|
|||||||
.class = class,
|
.class = class,
|
||||||
.rotation = rotation,
|
.rotation = rotation,
|
||||||
.visible = visible,
|
.visible = visible,
|
||||||
.shape = shape orelse return error.UnknownShapeType
|
.shape = shape orelse return error.UnknownShapeType,
|
||||||
|
.properties = properties
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -33,32 +33,48 @@ pub const List = struct {
|
|||||||
|
|
||||||
pub fn initFromXml(
|
pub fn initFromXml(
|
||||||
arena: std.mem.Allocator,
|
arena: std.mem.Allocator,
|
||||||
scratch: *std.heap.ArenaAllocator,
|
doc: xml.Document,
|
||||||
lexer: *xml.Lexer
|
element_id: xml.Element.Id
|
||||||
) !Property.List {
|
) !Property.List {
|
||||||
var iter = xml.TagParser.init(lexer);
|
_ = try doc.getTagExpect(element_id, "properties");
|
||||||
_ = try iter.begin("properties");
|
|
||||||
|
|
||||||
var temp_properties: std.ArrayList(Property) = .empty;
|
var properties: std.ArrayList(Property) = .empty;
|
||||||
|
try properties.ensureTotalCapacityPrecise(arena, doc.countChildren(element_id));
|
||||||
|
|
||||||
while (try iter.next()) |node| {
|
var iter = doc.iterChildren(element_id);
|
||||||
if (node.isTag("property")) {
|
while (iter.next()) |child_id| {
|
||||||
const property = try Property.initFromXml(arena, lexer);
|
const child = doc.getTag(child_id) orelse continue;
|
||||||
try temp_properties.append(scratch.allocator(), property);
|
const child_name = child.variant.tag.name;
|
||||||
continue;
|
|
||||||
|
if (std.mem.eql(u8, child_name, "property")) {
|
||||||
|
const property = try Property.initFromXml(arena, doc, child_id);
|
||||||
|
properties.appendAssumeCapacity(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
try iter.skip();
|
|
||||||
}
|
}
|
||||||
try iter.finish("properties");
|
|
||||||
|
|
||||||
const properties = try arena.dupe(Property, temp_properties.items);
|
|
||||||
|
|
||||||
return List{
|
return List{
|
||||||
.items = properties
|
.items = properties.items
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn initFromXmlContainer(
|
||||||
|
arena: std.mem.Allocator,
|
||||||
|
doc: xml.Document,
|
||||||
|
element_id: xml.Element.Id
|
||||||
|
) !Property.List {
|
||||||
|
var iter = doc.iterChildren(element_id);
|
||||||
|
while (iter.next()) |child_id| {
|
||||||
|
const child = doc.getTag(child_id) orelse continue;
|
||||||
|
const child_name = child.variant.tag.name;
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, child_name, "properties")) {
|
||||||
|
return try Property.List.initFromXml(arena, doc, child_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .empty;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(self: List, name: []const u8) ?Value {
|
pub fn get(self: List, name: []const u8) ?Value {
|
||||||
for (self.items) |item| {
|
for (self.items) |item| {
|
||||||
if (std.mem.eql(u8, item.name, name)) {
|
if (std.mem.eql(u8, item.name, name)) {
|
||||||
@ -70,7 +86,18 @@ pub const List = struct {
|
|||||||
|
|
||||||
pub fn getString(self: List, name: []const u8) ?[]const u8 {
|
pub fn getString(self: List, name: []const u8) ?[]const u8 {
|
||||||
if (self.get(name)) |value| {
|
if (self.get(name)) |value| {
|
||||||
return value.string;
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
@ -83,9 +110,9 @@ pub fn init(name: []const u8, value: Value) Property {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Property {
|
pub fn initFromXml(arena: std.mem.Allocator, doc: xml.Document, element_id: xml.Element.Id) !Property {
|
||||||
var iter = xml.TagParser.init(lexer);
|
const tag = try doc.getTagExpect(element_id, "property");
|
||||||
const attrs = try iter.begin("property");
|
const attrs = tag.variant.tag.attributes;
|
||||||
|
|
||||||
const name = try attrs.getDupe(arena, "name") orelse return error.MissingName;
|
const name = try attrs.getDupe(arena, "name") orelse return error.MissingName;
|
||||||
const prop_type_str = attrs.get("type") orelse "string";
|
const prop_type_str = attrs.get("type") orelse "string";
|
||||||
@ -104,8 +131,6 @@ pub fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Property {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try iter.finish("property");
|
|
||||||
|
|
||||||
return Property{
|
return Property{
|
||||||
.name = name,
|
.name = name,
|
||||||
.value = value
|
.value = value
|
||||||
|
|||||||
@ -12,5 +12,5 @@ pub const Tileset = @import("./tileset.zig");
|
|||||||
pub const Tilemap = @import("./tilemap.zig");
|
pub const Tilemap = @import("./tilemap.zig");
|
||||||
|
|
||||||
test {
|
test {
|
||||||
_ = std.testing.refAllDeclsRecursive(@This());
|
_ = std.testing.refAllDecls(@This());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
const log = std.log;
|
||||||
const xml = @import("./xml.zig");
|
const xml = @import("./xml.zig");
|
||||||
const Io = std.Io;
|
const Io = std.Io;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
@ -54,15 +55,13 @@ pub const TilesetReference = struct {
|
|||||||
source: []const u8,
|
source: []const u8,
|
||||||
first_gid: u32,
|
first_gid: u32,
|
||||||
|
|
||||||
pub fn initFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !TilesetReference {
|
pub fn initFromXml(arena: std.mem.Allocator, doc: xml.Document, element_id: xml.Element.Id) !TilesetReference {
|
||||||
var iter = xml.TagParser.init(lexer);
|
const tag = try doc.getTagExpect(element_id, "tileset");
|
||||||
const attrs = try iter.begin("tileset");
|
const attrs = tag.variant.tag.attributes;
|
||||||
|
|
||||||
const source = try attrs.getDupe(arena, "source") orelse return error.MissingFirstGid;
|
const source = try attrs.getDupe(arena, "source") orelse return error.MissingFirstGid;
|
||||||
const first_gid = try attrs.getNumber(u32, "firstgid") orelse return error.MissingFirstGid;
|
const first_gid = try attrs.getNumber(u32, "firstgid") orelse return error.MissingFirstGid;
|
||||||
|
|
||||||
try iter.finish("tileset");
|
|
||||||
|
|
||||||
return TilesetReference{
|
return TilesetReference{
|
||||||
.source = source,
|
.source = source,
|
||||||
.first_gid = first_gid
|
.first_gid = first_gid
|
||||||
@ -106,40 +105,20 @@ next_object_id: u32,
|
|||||||
tilesets: []TilesetReference,
|
tilesets: []TilesetReference,
|
||||||
layers: []Layer,
|
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:
|
// Map specification:
|
||||||
// https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#map
|
// https://doc.mapeditor.org/en/stable/reference/tmx-map-format/#map
|
||||||
pub fn initFromXml(
|
pub fn initFromXml(
|
||||||
gpa: std.mem.Allocator,
|
gpa: std.mem.Allocator,
|
||||||
scratch: *std.heap.ArenaAllocator,
|
doc: xml.Document,
|
||||||
lexer: *xml.Lexer,
|
element_id: xml.Element.Id
|
||||||
) !Tilemap {
|
) !Tilemap {
|
||||||
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
|
||||||
errdefer arena_allocator.deinit();
|
errdefer arena_allocator.deinit();
|
||||||
|
|
||||||
const arena = arena_allocator.allocator();
|
const arena = arena_allocator.allocator();
|
||||||
|
|
||||||
var iter = xml.TagParser.init(lexer);
|
const tag = try doc.getTagExpect(element_id, "map");
|
||||||
const map_attrs = try iter.begin("map");
|
const map_attrs = tag.variant.tag.attributes;
|
||||||
|
|
||||||
const version = try map_attrs.getDupe(arena, "version") orelse return error.MissingVersion;
|
const version = try map_attrs.getDupe(arena, "version") orelse return error.MissingVersion;
|
||||||
const tiled_version = try map_attrs.getDupe(arena, "tiledversion");
|
const tiled_version = try map_attrs.getDupe(arena, "tiledversion");
|
||||||
@ -165,26 +144,43 @@ pub fn initFromXml(
|
|||||||
// TODO: staggerindex
|
// TODO: staggerindex
|
||||||
}
|
}
|
||||||
|
|
||||||
var tileset_list: std.ArrayList(TilesetReference) = .empty;
|
var layer_count: usize = 0;
|
||||||
var layer_list: std.ArrayList(Layer) = .empty;
|
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;
|
||||||
|
|
||||||
while (try iter.next()) |node| {
|
if (std.mem.eql(u8, child_name, "tileset")) {
|
||||||
if (node.isTag("tileset")) {
|
tileset_count += 1;
|
||||||
try tileset_list.append(scratch.allocator(), try TilesetReference.initFromXml(arena, lexer));
|
} else {
|
||||||
continue;
|
layer_count += 1;
|
||||||
} else if (Layer.isLayerNode(node)) {
|
}
|
||||||
const layer = try Layer.initFromXml(
|
}
|
||||||
arena,
|
}
|
||||||
scratch,
|
|
||||||
lexer,
|
var tileset_list: std.ArrayList(TilesetReference) = .empty;
|
||||||
);
|
try tileset_list.ensureTotalCapacityPrecise(arena, tileset_count);
|
||||||
try layer_list.append(scratch.allocator(), layer);
|
|
||||||
continue;
|
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(
|
||||||
|
arena,
|
||||||
|
doc,
|
||||||
|
child_id
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
try iter.skip();
|
|
||||||
}
|
}
|
||||||
try iter.finish("map");
|
|
||||||
|
|
||||||
const tilesets = try arena.dupe(TilesetReference, tileset_list.items);
|
const tilesets = try arena.dupe(TilesetReference, tileset_list.items);
|
||||||
const layers = try arena.dupe(Layer, layer_list.items);
|
const layers = try arena.dupe(Layer, layer_list.items);
|
||||||
@ -223,7 +219,7 @@ fn getTilesetByGid(self: *const Tilemap, gid: u32) ?TilesetReference {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getTile(self: *const Tilemap, layer: *const Layer, tilesets: Tileset.List, x: usize, y: usize) ?Tile {
|
pub fn getTile(self: *const Tilemap, layer: *const Layer, tilesets: Tileset.List, x: i32, y: i32) ?Tile {
|
||||||
assert(layer.variant == .tile);
|
assert(layer.variant == .tile);
|
||||||
const tile_variant = layer.variant.tile;
|
const tile_variant = layer.variant.tile;
|
||||||
|
|
||||||
@ -248,10 +244,10 @@ pub fn getLayer(self: *const Tilemap, name: []const u8) ?*const Layer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub const Bounds = struct {
|
pub const Bounds = struct {
|
||||||
min_x: usize,
|
min_x: i32,
|
||||||
min_y: usize,
|
min_y: i32,
|
||||||
max_x: usize,
|
max_x: i32,
|
||||||
max_y: usize,
|
max_y: i32,
|
||||||
|
|
||||||
const zero = Bounds{
|
const zero = Bounds{
|
||||||
.min_x = 0,
|
.min_x = 0,
|
||||||
@ -260,12 +256,16 @@ pub const Bounds = struct {
|
|||||||
.max_y = 0,
|
.max_y = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn width(self: Bounds) usize {
|
pub fn width(self: Bounds) u32 {
|
||||||
return self.max_x - self.min_x + 1;
|
return @intCast(self.max_x - self.min_x + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn height(self: Bounds) usize {
|
pub fn height(self: Bounds) u32 {
|
||||||
return self.max_y - self.min_y + 1;
|
return @intCast(self.max_y - self.min_y + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isInside(self: Bounds, x: i32, y: i32) bool {
|
||||||
|
return (self.min_x <= x and x <= self.max_x) and (self.min_y <= y and y <= self.max_y);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -277,26 +277,22 @@ pub fn getTileBounds(self: *const Tilemap) Bounds {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (0..self.height) |y| {
|
for (layer.variant.tile.chunks) |chunk| {
|
||||||
for (0..self.width) |x| {
|
const chunk_bounds = Bounds{
|
||||||
const gid = layer.variant.tile.get(x, y).?;
|
.min_x = chunk.x,
|
||||||
if (gid != 0) {
|
.min_y = chunk.y,
|
||||||
if (result) |bounds| {
|
.max_x = chunk.x + @as(i32, @intCast(chunk.width)),
|
||||||
result = Bounds{
|
.max_y = chunk.y + @as(i32, @intCast(chunk.height))
|
||||||
.min_x = @min(x, bounds.min_x),
|
};
|
||||||
.min_y = @min(y, bounds.min_y),
|
if (result) |bounds| {
|
||||||
.max_x = @max(x, bounds.max_x),
|
result = Bounds{
|
||||||
.max_y = @max(y, bounds.max_y)
|
.min_x = @min(chunk_bounds.min_x, bounds.min_x),
|
||||||
};
|
.min_y = @min(chunk_bounds.min_y, bounds.min_y),
|
||||||
} else {
|
.max_x = @max(chunk_bounds.max_x, bounds.max_x),
|
||||||
result = Bounds{
|
.max_y = @max(chunk_bounds.max_y, bounds.max_y)
|
||||||
.min_x = x,
|
};
|
||||||
.min_y = y,
|
} else {
|
||||||
.max_x = x,
|
result = chunk_bounds;
|
||||||
.max_y = y
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,6 +300,59 @@ pub fn getTileBounds(self: *const Tilemap) Bounds {
|
|||||||
return result orelse Bounds.zero;
|
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 {
|
pub fn deinit(self: *const Tilemap) void {
|
||||||
self.arena.deinit();
|
self.arena.deinit();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,16 +14,14 @@ pub const Image = struct {
|
|||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
|
||||||
pub fn intFromXml(arena: std.mem.Allocator, lexer: *xml.Lexer) !Image {
|
pub fn intFromXml(arena: std.mem.Allocator, doc: xml.Document, element_id: xml.Element.Id) !Image {
|
||||||
var iter = xml.TagParser.init(lexer);
|
const tag = try doc.getTagExpect(element_id, "image");
|
||||||
const attrs = try iter.begin("image");
|
const attrs = tag.variant.tag.attributes;
|
||||||
|
|
||||||
const source = try attrs.getDupe(arena, "width") orelse return error.MissingSource;
|
const source = try attrs.getDupe(arena, "width") orelse return error.MissingSource;
|
||||||
const width = try attrs.getNumber(u32, "width") orelse return error.MissingWidth;
|
const width = try attrs.getNumber(u32, "width") orelse return error.MissingWidth;
|
||||||
const height = try attrs.getNumber(u32, "height") orelse return error.MissingHeight;
|
const height = try attrs.getNumber(u32, "height") orelse return error.MissingHeight;
|
||||||
|
|
||||||
try iter.finish("image");
|
|
||||||
|
|
||||||
return Image{
|
return Image{
|
||||||
.source = source,
|
.source = source,
|
||||||
.width = width,
|
.width = width,
|
||||||
@ -38,26 +36,14 @@ pub const Tile = struct {
|
|||||||
|
|
||||||
pub fn initFromXml(
|
pub fn initFromXml(
|
||||||
arena: std.mem.Allocator,
|
arena: std.mem.Allocator,
|
||||||
scratch: *std.heap.ArenaAllocator,
|
doc: xml.Document,
|
||||||
lexer: *xml.Lexer,
|
element_id: xml.Element.Id
|
||||||
) !Tile {
|
) !Tile {
|
||||||
var iter = xml.TagParser.init(lexer);
|
const tag = try doc.getTagExpect(element_id, "tile");
|
||||||
const tile_attrs = try iter.begin("tile");
|
const tile_attrs = tag.variant.tag.attributes;
|
||||||
|
|
||||||
const id = try tile_attrs.getNumber(u32, "id") orelse return error.MissingId;
|
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{
|
return Tile{
|
||||||
.id = id,
|
.id = id,
|
||||||
@ -124,36 +110,16 @@ image: Image,
|
|||||||
|
|
||||||
tiles: []Tile,
|
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(
|
pub fn initFromXml(
|
||||||
gpa: std.mem.Allocator,
|
gpa: std.mem.Allocator,
|
||||||
scratch: *std.heap.ArenaAllocator,
|
doc: xml.Document,
|
||||||
lexer: *xml.Lexer
|
element_id: xml.Element.Id
|
||||||
) !Tileset {
|
) !Tileset {
|
||||||
var arena_state = std.heap.ArenaAllocator.init(gpa);
|
var arena_state = std.heap.ArenaAllocator.init(gpa);
|
||||||
const arena = arena_state.allocator();
|
const arena = arena_state.allocator();
|
||||||
|
|
||||||
var iter = xml.TagParser.init(lexer);
|
const tag = try doc.getTagExpect(element_id, "tileset");
|
||||||
const tileset_attrs = try iter.begin("tileset");
|
const tileset_attrs = tag.variant.tag.attributes;
|
||||||
|
|
||||||
const version = try tileset_attrs.getDupe(arena, "version") orelse return error.MissingTilesetTag;
|
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;
|
const tiled_version = try tileset_attrs.getDupe(arena, "tiledversion") orelse return error.MissingTilesetTag;
|
||||||
@ -165,20 +131,20 @@ pub fn initFromXml(
|
|||||||
|
|
||||||
var image: ?Image = null;
|
var image: ?Image = null;
|
||||||
var tiles_list: std.ArrayList(Tile) = .empty;
|
var tiles_list: std.ArrayList(Tile) = .empty;
|
||||||
|
try tiles_list.ensureTotalCapacityPrecise(arena, doc.countChildren(element_id));
|
||||||
|
|
||||||
while (try iter.next()) |node| {
|
var iter = doc.iterChildren(element_id);
|
||||||
if (node.isTag("image")) {
|
while (iter.next()) |child_id| {
|
||||||
image = try Image.intFromXml(arena, lexer);
|
const child = doc.getTag(child_id) orelse continue;
|
||||||
continue;
|
const child_name = child.variant.tag.name;
|
||||||
} else if (node.isTag("tile")) {
|
|
||||||
const tile = try Tile.initFromXml(arena, scratch, lexer);
|
if (std.mem.eql(u8, child_name, "image")) {
|
||||||
try tiles_list.append(scratch.allocator(), tile);
|
image = try Image.intFromXml(arena, doc, child_id);
|
||||||
continue;
|
} 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);
|
const tiles = try arena.dupe(Tile, tiles_list.items);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const Io = std.Io;
|
const Io = std.Io;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
const Color = @import("./color.zig");
|
const Color = @import("./color.zig");
|
||||||
@ -105,199 +106,124 @@ pub const Attribute = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Tag = struct {
|
pub const Element = struct {
|
||||||
name: []const u8,
|
variant: union(enum) {
|
||||||
attributes: Attribute.List
|
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);
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Lexer = struct {
|
pub const Document = struct {
|
||||||
pub const Buffers = struct {
|
elements: []Element,
|
||||||
scratch: std.heap.ArenaAllocator,
|
|
||||||
text: std.ArrayList(u8),
|
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) Buffers {
|
pub fn get(self: Document, id: Element.Id) ?Element {
|
||||||
return Buffers{
|
if (id == Element.Id.nil) {
|
||||||
.scratch = std.heap.ArenaAllocator.init(allocator),
|
return null;
|
||||||
.text = .empty
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
const index = @intFromEnum(id);
|
||||||
|
assert(index >= 0);
|
||||||
|
return self.elements[@intCast(index)];
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear(self: *Buffers) void {
|
pub fn getTag(self: Document, id: Element.Id) ?Element {
|
||||||
self.text.clearRetainingCapacity();
|
const element = self.get(id) orelse return null;
|
||||||
_ = self.scratch.reset(.retain_capacity);
|
if (element.variant == .tag) {
|
||||||
|
return element;
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Buffers) void {
|
pub fn getTagExpect(self: Document, id: Element.Id, name: []const u8) !Element {
|
||||||
const allocator = self.scratch.child_allocator;
|
const tag = self.getTag(id) orelse return error.TagNotFound;
|
||||||
self.scratch.deinit();
|
const tag_name = tag.variant.tag.name;
|
||||||
self.text.deinit(allocator);
|
if (!std.mem.eql(u8, tag_name, name)) {
|
||||||
|
return error.UnexpectedTagName;
|
||||||
}
|
}
|
||||||
};
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
pub const Token = union(enum) {
|
pub fn iterChildren(self: Document, id: Element.Id) SiblingIterator {
|
||||||
start_tag: Tag,
|
var first_child: Element.Id = .nil;
|
||||||
end_tag: []const u8,
|
if (self.get(id)) |tag| {
|
||||||
text: []const u8,
|
first_child = tag.tree.first_child;
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
return SiblingIterator{
|
||||||
pub fn isEndTag(self: Token, name: []const u8) bool {
|
.doc = self,
|
||||||
if (self == .end_tag) {
|
.current = first_child
|
||||||
return std.mem.eql(u8, self.end_tag, name);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const StepResult = struct {
|
|
||||||
token: ?Token,
|
|
||||||
self_closing: bool = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
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
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn step(self: *Lexer) !StepResult {
|
pub fn countChildren(self: Document, id: Element.Id) usize {
|
||||||
_ = self.buffers.scratch.reset(.retain_capacity);
|
var count: usize = 0;
|
||||||
|
var iter = self.iterChildren(id);
|
||||||
if (try self.peekByte() == '<') {
|
while (iter.next()) |_| {
|
||||||
self.tossByte();
|
count += 1;
|
||||||
|
|
||||||
if (try self.peekByte() == '/') {
|
|
||||||
// End tag
|
|
||||||
self.tossByte();
|
|
||||||
|
|
||||||
const name = try self.parseName();
|
|
||||||
try self.skipWhiteSpace();
|
|
||||||
|
|
||||||
if (!std.mem.eql(u8, try self.takeBytes(1), ">")) {
|
|
||||||
return error.InvalidEndTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = Token{ .end_tag = name };
|
|
||||||
return .{ .token = token };
|
|
||||||
|
|
||||||
} else if (try self.peekByte() == '?') {
|
|
||||||
// Prolog tag
|
|
||||||
self.tossByte();
|
|
||||||
if (!std.mem.eql(u8, try self.takeBytes(4), "xml ")) {
|
|
||||||
return error.InvalidPrologTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
const attributes = try self.parseAttributes();
|
|
||||||
try self.skipWhiteSpace();
|
|
||||||
|
|
||||||
if (!std.mem.eql(u8, try self.takeBytes(2), "?>")) {
|
|
||||||
return error.MissingPrologEnd;
|
|
||||||
}
|
|
||||||
|
|
||||||
const version = attributes.get("version") orelse return error.InvalidProlog;
|
|
||||||
if (!std.mem.eql(u8, version, "1.0")) {
|
|
||||||
return error.InvalidPrologVersion;
|
|
||||||
}
|
|
||||||
const encoding = attributes.get("encoding") orelse return error.InvalidProlog;
|
|
||||||
if (!std.mem.eql(u8, encoding, "UTF-8")) {
|
|
||||||
return error.InvalidPrologEncoding;
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{ .token = null };
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Start tag
|
|
||||||
const name = try self.parseName();
|
|
||||||
const attributes = try self.parseAttributes();
|
|
||||||
try self.skipWhiteSpace();
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
const text_start = self.cursor;
|
|
||||||
while (try self.peekByte() != '<') {
|
|
||||||
self.tossByte();
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
token = Token{ .text = text };
|
|
||||||
}
|
|
||||||
return .{ .token = token };
|
|
||||||
}
|
}
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next(self: *Lexer) !?Token {
|
const SiblingIterator = struct {
|
||||||
if (self.peeked_value) |value| {
|
doc: Document,
|
||||||
self.peeked_value = null;
|
current: Element.Id,
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
pub fn next(self: *SiblingIterator) ?Element.Id {
|
||||||
|
const element = self.doc.get(self.current) orelse return null;
|
||||||
|
const current = self.current;
|
||||||
|
self.current = element.tree.next_sibling;
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Lexer = struct {
|
||||||
|
pub const Token = union(enum) {
|
||||||
|
start_tag_begin: []const u8,
|
||||||
|
attribute: Attribute,
|
||||||
|
start_tag_end: []const u8,
|
||||||
|
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}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
text: []const u8,
|
||||||
|
cursor: usize = 0,
|
||||||
|
inside_start_tag: ?[]const u8 = null,
|
||||||
|
queued_end_tag: ?[]const u8 = null,
|
||||||
|
|
||||||
|
pub fn next(self: *Lexer) !?Token {
|
||||||
if (self.queued_end_tag) |name| {
|
if (self.queued_end_tag) |name| {
|
||||||
self.queued_end_tag = null;
|
self.queued_end_tag = null;
|
||||||
return Token{
|
return Token{
|
||||||
@ -305,39 +231,103 @@ pub const Lexer = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (self.cursor < self.text.len) {
|
||||||
if (self.buffers.text.items.len == 0) {
|
if (self.inside_start_tag) |tag_name| {
|
||||||
self.readIntoTextBuffer() catch |e| switch (e) {
|
if (try self.parseAttribute()) |attribute| {
|
||||||
error.EndOfStream => break,
|
return Token{
|
||||||
else => return e
|
.attribute = attribute
|
||||||
};
|
};
|
||||||
}
|
} else {
|
||||||
|
try self.skipWhiteSpace();
|
||||||
|
|
||||||
const saved_cursor = self.cursor;
|
if (std.mem.eql(u8, try self.peekBytes(1), ">")) {
|
||||||
const result = self.step() catch |e| switch(e) {
|
self.tossBytes(1);
|
||||||
error.EndOfTextBuffer => {
|
|
||||||
self.cursor = saved_cursor;
|
} else if (std.mem.eql(u8, try self.peekBytes(2), "/>")) {
|
||||||
|
self.tossBytes(2);
|
||||||
|
|
||||||
|
self.queued_end_tag = tag_name;
|
||||||
|
|
||||||
const unused_capacity = self.buffers.text.capacity - self.buffers.text.items.len;
|
|
||||||
if (unused_capacity == 0 and self.cursor > 0) {
|
|
||||||
self.rebaseBuffer();
|
|
||||||
} else {
|
} else {
|
||||||
self.readIntoTextBuffer() catch |read_err| switch (read_err) {
|
return error.UnfinishedStartTag;
|
||||||
error.EndOfStream => break,
|
|
||||||
else => return read_err
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
self.inside_start_tag = null;
|
||||||
},
|
|
||||||
else => return e
|
|
||||||
};
|
|
||||||
|
|
||||||
if (result.token) |token| {
|
return Token{
|
||||||
if (token == .start_tag and result.self_closing) {
|
.start_tag_end = tag_name
|
||||||
self.queued_end_tag = token.start_tag.name;
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (try self.peekByte() == '<') {
|
||||||
|
self.tossByte();
|
||||||
|
|
||||||
|
if (try self.peekByte() == '/') {
|
||||||
|
// End tag
|
||||||
|
self.tossByte();
|
||||||
|
|
||||||
|
const name = try self.parseName();
|
||||||
|
try self.skipWhiteSpace();
|
||||||
|
|
||||||
|
if (!std.mem.eql(u8, try self.takeBytes(1), ">")) {
|
||||||
|
return error.InvalidEndTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Token{ .end_tag = name };
|
||||||
|
|
||||||
|
} else if (try self.peekByte() == '?') {
|
||||||
|
// Prolog tag
|
||||||
|
self.tossByte();
|
||||||
|
if (!std.mem.eql(u8, try self.takeBytes(4), "xml ")) {
|
||||||
|
return error.InvalidPrologTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
var attributes_buf: [8]Attribute = undefined;
|
||||||
|
const attributes_len = try self.parseAttributesBounded(&attributes_buf);
|
||||||
|
try self.skipWhiteSpace();
|
||||||
|
|
||||||
|
const attributes = Attribute.List{
|
||||||
|
.items = attributes_buf[0..attributes_len]
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!std.mem.eql(u8, try self.takeBytes(2), "?>")) {
|
||||||
|
return error.MissingPrologEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
const version = attributes.get("version") orelse return error.InvalidProlog;
|
||||||
|
if (!std.mem.eql(u8, version, "1.0")) {
|
||||||
|
return error.InvalidPrologVersion;
|
||||||
|
}
|
||||||
|
const encoding = attributes.get("encoding") orelse return error.InvalidProlog;
|
||||||
|
if (!std.mem.eql(u8, encoding, "UTF-8")) {
|
||||||
|
return error.InvalidPrologEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Start tag
|
||||||
|
const name = try self.parseName();
|
||||||
|
self.inside_start_tag = name;
|
||||||
|
|
||||||
|
return Token{
|
||||||
|
.start_tag_begin = name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
try self.skipWhiteSpace();
|
||||||
|
if (!self.hasBytes(1)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text_start = self.cursor;
|
||||||
|
while (try self.peekByte() != '<') {
|
||||||
|
self.tossByte();
|
||||||
|
}
|
||||||
|
var text: []const u8 = self.text[text_start..self.cursor];
|
||||||
|
text = std.mem.trimEnd(u8, text, &std.ascii.whitespace);
|
||||||
|
if (text.len > 0) {
|
||||||
|
return Token{ .text = text };
|
||||||
}
|
}
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -381,45 +371,6 @@ pub const Lexer = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn peek(self: *Lexer) !?Token {
|
|
||||||
if (try self.next()) |value| {
|
|
||||||
self.peeked_value = value;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn readIntoTextBuffer(self: *Lexer) !void {
|
|
||||||
const gpa = self.buffers.scratch.child_allocator;
|
|
||||||
const text = &self.buffers.text;
|
|
||||||
try text.ensureUnusedCapacity(gpa, 1);
|
|
||||||
|
|
||||||
var writer = Io.Writer.fixed(text.allocatedSlice());
|
|
||||||
writer.end = text.items.len;
|
|
||||||
|
|
||||||
_ = self.io_reader.stream(&writer, .limited(text.capacity - text.items.len)) catch |e| switch (e) {
|
|
||||||
error.WriteFailed => unreachable,
|
|
||||||
else => |ee| return ee
|
|
||||||
};
|
|
||||||
text.items.len = writer.end;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rebaseBuffer(self: *Lexer) void {
|
|
||||||
if (self.cursor == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const text = &self.buffers.text;
|
|
||||||
@memmove(
|
|
||||||
text.items[0..(text.items.len - self.cursor)],
|
|
||||||
text.items[self.cursor..]
|
|
||||||
);
|
|
||||||
|
|
||||||
text.items.len -= self.cursor;
|
|
||||||
self.cursor = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn isNameStartChar(c: u8) bool {
|
fn isNameStartChar(c: u8) bool {
|
||||||
return c == ':' or c == '_' or std.ascii.isAlphabetic(c);
|
return c == ':' or c == '_' or std.ascii.isAlphabetic(c);
|
||||||
}
|
}
|
||||||
@ -429,16 +380,14 @@ pub const Lexer = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn hasBytes(self: *Lexer, n: usize) bool {
|
fn hasBytes(self: *Lexer, n: usize) bool {
|
||||||
const text = self.buffers.text.items;
|
return self.cursor + n <= self.text.len;
|
||||||
return self.cursor + n <= text.len;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn peekBytes(self: *Lexer, n: usize) ![]const u8 {
|
fn peekBytes(self: *Lexer, n: usize) ![]const u8 {
|
||||||
if (self.hasBytes(n)) {
|
if (self.hasBytes(n)) {
|
||||||
const text = self.buffers.text.items;
|
return self.text[self.cursor..][0..n];
|
||||||
return text[self.cursor..][0..n];
|
|
||||||
}
|
}
|
||||||
return error.EndOfTextBuffer;
|
return error.UnexpectedEOF;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tossBytes(self: *Lexer, n: usize) void {
|
fn tossBytes(self: *Lexer, n: usize) void {
|
||||||
@ -475,11 +424,11 @@ pub const Lexer = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.buffers.text.items[name_start..self.cursor];
|
return self.text[name_start..self.cursor];
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skipWhiteSpace(self: *Lexer) !void {
|
fn skipWhiteSpace(self: *Lexer) !void {
|
||||||
while (std.ascii.isWhitespace(try self.peekByte())) {
|
while (self.cursor < self.text.len and std.ascii.isWhitespace(try self.peekByte())) {
|
||||||
self.tossByte();
|
self.tossByte();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -504,184 +453,304 @@ pub const Lexer = struct {
|
|||||||
value_len += 1;
|
value_len += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.buffers.text.items[value_start..][0..value_len];
|
return self.text[value_start..][0..value_len];
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseAttributes(self: *Lexer) !Attribute.List {
|
fn parseAttribute(self: *Lexer) !?Attribute {
|
||||||
const arena = self.buffers.scratch.allocator();
|
try self.skipWhiteSpace();
|
||||||
var attributes: std.ArrayList(Attribute) = .empty;
|
const name = try self.parseName();
|
||||||
|
if (name.len == 0) {
|
||||||
while (true) {
|
|
||||||
try self.skipWhiteSpace();
|
|
||||||
const name = try self.parseName();
|
|
||||||
if (name.len == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
try self.skipWhiteSpace();
|
|
||||||
if (try self.takeByte() != '=') {
|
|
||||||
return error.MissingAttributeEquals;
|
|
||||||
}
|
|
||||||
try self.skipWhiteSpace();
|
|
||||||
const value = try self.parseAttributeValue();
|
|
||||||
|
|
||||||
const list = Attribute.List{ .items = attributes.items };
|
|
||||||
if (list.get(name) != null) {
|
|
||||||
return error.DuplicateAttribute;
|
|
||||||
}
|
|
||||||
|
|
||||||
try attributes.append(arena, Attribute{
|
|
||||||
.name = name,
|
|
||||||
.value = value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Attribute.List{
|
|
||||||
.items = attributes.items
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test "self closing tag" {
|
|
||||||
const allocator = std.testing.allocator;
|
|
||||||
var ctx: TestingContext = undefined;
|
|
||||||
ctx.init(allocator,
|
|
||||||
\\ <hello />
|
|
||||||
);
|
|
||||||
defer ctx.deinit();
|
|
||||||
|
|
||||||
try std.testing.expect((try ctx.lexer.next()).?.isStartTag("hello"));
|
|
||||||
try std.testing.expect((try ctx.lexer.next()).?.isEndTag("hello"));
|
|
||||||
try std.testing.expect((try ctx.lexer.next()) == null);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "tag" {
|
|
||||||
const allocator = std.testing.allocator;
|
|
||||||
var ctx: TestingContext = undefined;
|
|
||||||
ctx.init(allocator,
|
|
||||||
\\ <hello></hello>
|
|
||||||
);
|
|
||||||
defer ctx.deinit();
|
|
||||||
|
|
||||||
try std.testing.expect((try ctx.lexer.next()).?.isStartTag("hello"));
|
|
||||||
try std.testing.expect((try ctx.lexer.next()).?.isEndTag("hello"));
|
|
||||||
try std.testing.expect((try ctx.lexer.next()) == null);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "tag with prolog" {
|
|
||||||
const allocator = std.testing.allocator;
|
|
||||||
var ctx: TestingContext = undefined;
|
|
||||||
ctx.init(allocator,
|
|
||||||
\\ <?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
\\ <hello></hello>
|
|
||||||
);
|
|
||||||
defer ctx.deinit();
|
|
||||||
|
|
||||||
try std.testing.expect((try ctx.lexer.next()).?.isStartTag("hello"));
|
|
||||||
try std.testing.expect((try ctx.lexer.next()).?.isEndTag("hello"));
|
|
||||||
try std.testing.expect((try ctx.lexer.next()) == null);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "text content" {
|
|
||||||
const allocator = std.testing.allocator;
|
|
||||||
var ctx: TestingContext = undefined;
|
|
||||||
ctx.init(allocator,
|
|
||||||
\\ <hello> Hello World </hello>
|
|
||||||
);
|
|
||||||
defer ctx.deinit();
|
|
||||||
|
|
||||||
try std.testing.expect((try ctx.lexer.next()).?.isStartTag("hello"));
|
|
||||||
try std.testing.expectEqualStrings("Hello World", (try ctx.lexer.next()).?.text);
|
|
||||||
try std.testing.expect((try ctx.lexer.next()).?.isEndTag("hello"));
|
|
||||||
try std.testing.expect((try ctx.lexer.next()) == null);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "attributes" {
|
|
||||||
const allocator = std.testing.allocator;
|
|
||||||
var ctx: TestingContext = undefined;
|
|
||||||
ctx.init(allocator,
|
|
||||||
\\ <hello a='1' b='2'/>
|
|
||||||
);
|
|
||||||
defer ctx.deinit();
|
|
||||||
|
|
||||||
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").?);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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 null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return switch (value) {
|
try self.skipWhiteSpace();
|
||||||
.text => |text| Node{ .text = text },
|
if (try self.takeByte() != '=') {
|
||||||
.start_tag => |start_tag| Node{ .tag = start_tag },
|
return error.MissingAttributeEquals;
|
||||||
.end_tag => unreachable,
|
}
|
||||||
|
try self.skipWhiteSpace();
|
||||||
|
const value = try self.parseAttributeValue();
|
||||||
|
|
||||||
|
return Attribute{
|
||||||
|
.name = name,
|
||||||
|
.value = value
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn skip(self: *TagParser) !void {
|
fn parseAttributesBounded(self: *Lexer, buffer: []Attribute) !usize {
|
||||||
assert(self.begin_called);
|
var attributes: std.ArrayList(Attribute) = .initBuffer(buffer);
|
||||||
assert(!self.finish_called);
|
|
||||||
|
|
||||||
const value = try self.lexer.next() orelse return error.MissingNode;
|
while (try self.parseAttribute()) |attr| {
|
||||||
if (value == .end_tag) {
|
const list = Attribute.List{ .items = attributes.items };
|
||||||
return error.UnexpectedEndTag;
|
if (list.get(attr.name) != null) {
|
||||||
} else if (value == .start_tag) {
|
return error.DuplicateAttribute;
|
||||||
// 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);
|
try attributes.appendBounded(attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return attributes.items.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expectStartTagBegin(lexer: *Lexer, expected_name: []const u8) !void {
|
||||||
|
const token = (try lexer.next()).?;
|
||||||
|
try std.testing.expect(token == .start_tag_begin);
|
||||||
|
try std.testing.expectEqualStrings(expected_name, token.start_tag_begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expectStartTagEnd(lexer: *Lexer) !void {
|
||||||
|
const token = (try lexer.next()).?;
|
||||||
|
try std.testing.expect(token == .start_tag_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expectEndTag(lexer: *Lexer, expected_name: []const u8) !void {
|
||||||
|
const token = (try lexer.next()).?;
|
||||||
|
try std.testing.expect(token == .end_tag);
|
||||||
|
try std.testing.expectEqualStrings(expected_name, token.end_tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expectDone(lexer: *Lexer) !void {
|
||||||
|
try std.testing.expect(try lexer.next() == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expectText(lexer: *Lexer, expected_text: []const u8) !void {
|
||||||
|
const token = (try lexer.next()).?;
|
||||||
|
try std.testing.expect(token == .text);
|
||||||
|
try std.testing.expectEqualStrings(expected_text, token.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expectAttribute(lexer: *Lexer, expected: Attribute) !void {
|
||||||
|
const token = (try lexer.next()).?;
|
||||||
|
try std.testing.expect(token == .attribute);
|
||||||
|
try std.testing.expectEqualStrings(expected.name, token.attribute.name);
|
||||||
|
try std.testing.expectEqualStrings(expected.value, token.attribute.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "self closing tag" {
|
||||||
|
const text =
|
||||||
|
\\ <hello />
|
||||||
|
;
|
||||||
|
|
||||||
|
var lexer: Lexer = .{ .text = text };
|
||||||
|
try expectStartTagBegin(&lexer, "hello");
|
||||||
|
try expectStartTagEnd(&lexer);
|
||||||
|
try expectEndTag(&lexer, "hello");
|
||||||
|
try expectDone(&lexer);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "tag" {
|
||||||
|
const text =
|
||||||
|
\\ <hello></hello>
|
||||||
|
;
|
||||||
|
|
||||||
|
var lexer: Lexer = .{ .text = text };
|
||||||
|
try expectStartTagBegin(&lexer, "hello");
|
||||||
|
try expectStartTagEnd(&lexer);
|
||||||
|
try expectEndTag(&lexer, "hello");
|
||||||
|
try expectDone(&lexer);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "tag with prolog" {
|
||||||
|
const text =
|
||||||
|
\\ <?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
\\ <hello></hello>
|
||||||
|
;
|
||||||
|
|
||||||
|
var lexer: Lexer = .{ .text = text };
|
||||||
|
try expectStartTagBegin(&lexer, "hello");
|
||||||
|
try expectStartTagEnd(&lexer);
|
||||||
|
try expectEndTag(&lexer, "hello");
|
||||||
|
try expectDone(&lexer);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "text content" {
|
||||||
|
const text =
|
||||||
|
\\ <hello> Hello World </hello>
|
||||||
|
;
|
||||||
|
|
||||||
|
var lexer: Lexer = .{ .text = text };
|
||||||
|
try expectStartTagBegin(&lexer, "hello");
|
||||||
|
try expectStartTagEnd(&lexer);
|
||||||
|
try expectText(&lexer, "Hello World");
|
||||||
|
try expectEndTag(&lexer, "hello");
|
||||||
|
try expectDone(&lexer);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "attributes" {
|
||||||
|
const text =
|
||||||
|
\\ <hello a='1' b='2'/>
|
||||||
|
;
|
||||||
|
|
||||||
|
var lexer: Lexer = .{ .text = text };
|
||||||
|
try expectStartTagBegin(&lexer, "hello");
|
||||||
|
try expectAttribute(&lexer, .{ .name = "a", .value = "1" });
|
||||||
|
try expectAttribute(&lexer, .{ .name = "b", .value = "2" });
|
||||||
|
try expectStartTagEnd(&lexer);
|
||||||
|
try expectEndTag(&lexer, "hello");
|
||||||
|
try expectDone(&lexer);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Parser = struct {
|
||||||
|
elements: []Element,
|
||||||
|
attributes: []Attribute,
|
||||||
|
stack: []Element.Id,
|
||||||
|
|
||||||
|
const InitOptions = struct {
|
||||||
|
max_elements: u32 = 4096,
|
||||||
|
max_attributes: u32 = 4096,
|
||||||
|
max_depth: u32 = 256,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn initCapacity(gpa: Allocator, opts: InitOptions) !Parser {
|
||||||
|
const tags = try gpa.alloc(Element, opts.max_elements);
|
||||||
|
errdefer gpa.free(tags);
|
||||||
|
|
||||||
|
const attributes = try gpa.alloc(Attribute, opts.max_attributes);
|
||||||
|
errdefer gpa.free(attributes);
|
||||||
|
|
||||||
|
const stack = try gpa.alloc(Element.Id, opts.max_depth);
|
||||||
|
errdefer gpa.free(stack);
|
||||||
|
|
||||||
|
return Parser{
|
||||||
|
.elements = tags,
|
||||||
|
.attributes = attributes,
|
||||||
|
.stack = stack,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: Parser, gpa: Allocator) void {
|
||||||
|
gpa.free(self.elements);
|
||||||
|
gpa.free(self.attributes);
|
||||||
|
gpa.free(self.stack);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ParserContext = struct {
|
||||||
|
elements: std.ArrayList(Element),
|
||||||
|
stack: std.ArrayList(Element.Id),
|
||||||
|
|
||||||
|
fn getElement(self: *ParserContext, id: Element.Id) *Element {
|
||||||
|
return &self.elements.items[@intCast(@intFromEnum(id))];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pushElement(self: *ParserContext) !*Element {
|
||||||
|
const element_index = self.elements.items.len;
|
||||||
|
const element_id: Element.Id = @enumFromInt(element_index);
|
||||||
|
|
||||||
|
const element = self.elements.addOneBounded() catch return error.TooManyElements;
|
||||||
|
element.tree = .{};
|
||||||
|
|
||||||
|
if (self.stack.getLastOrNull()) |parent_id| {
|
||||||
|
const parent = self.getElement(parent_id);
|
||||||
|
if (parent.tree.last_child != Element.Id.nil) {
|
||||||
|
const last_child = &self.elements.items[@intCast(@intFromEnum(parent.tree.last_child))];
|
||||||
|
last_child.tree.next_sibling = element_id;
|
||||||
|
} else {
|
||||||
|
parent.tree.first_child = element_id;
|
||||||
|
}
|
||||||
|
parent.tree.last_child = element_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.stack.appendBounded(element_id) catch return error.StackTooDeep;
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn popElement(self: *ParserContext) !void {
|
||||||
|
if (self.stack.items.len == 0) {
|
||||||
|
return error.UnbalancedBeginAndEndTags;
|
||||||
|
}
|
||||||
|
_ = self.stack.pop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn parse(self: Parser, text: []const u8) !Document {
|
||||||
|
var ctx = ParserContext{
|
||||||
|
.elements = .initBuffer(self.elements),
|
||||||
|
.stack = .initBuffer(self.stack)
|
||||||
|
};
|
||||||
|
|
||||||
|
var attributes_used: usize = 0;
|
||||||
|
var attributes: std.ArrayList(Attribute) = .initBuffer(self.attributes[attributes_used..]);
|
||||||
|
|
||||||
|
var lexer = Lexer{ .text = text };
|
||||||
|
while (try lexer.next()) |token| {
|
||||||
|
switch (token) {
|
||||||
|
.start_tag_begin => |name| {
|
||||||
|
_ = name; // autofix
|
||||||
|
},
|
||||||
|
.attribute => |attribute| {
|
||||||
|
attributes.appendBounded(attribute) catch return error.TooManyAttributes;
|
||||||
|
},
|
||||||
|
.start_tag_end => |name| {
|
||||||
|
const element = try ctx.pushElement();
|
||||||
|
element.variant = .{
|
||||||
|
.tag = .{
|
||||||
|
.name = name,
|
||||||
|
.attributes = .{ .items = attributes.items }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
attributes_used += attributes.items.len;
|
||||||
|
attributes = .initBuffer(self.attributes[attributes_used..]);
|
||||||
|
},
|
||||||
|
.end_tag => {
|
||||||
|
try ctx.popElement();
|
||||||
|
},
|
||||||
|
.text => |element_text| {
|
||||||
|
const element = try ctx.pushElement();
|
||||||
|
element.variant = .{
|
||||||
|
.text = element_text
|
||||||
|
};
|
||||||
|
|
||||||
|
try ctx.popElement();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.stack.items.len > 0) {
|
||||||
|
return error.UnbalancedBeginAndEndTags;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Document{
|
||||||
|
.elements = ctx.elements.items
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
const gpa = std.testing.allocator;
|
||||||
|
var parser = try Parser.initCapacity(gpa, .{});
|
||||||
|
defer parser.deinit(gpa);
|
||||||
|
|
||||||
|
const doc = try parser.parse(
|
||||||
|
\\ <text>Hello, World!</text>
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test {
|
||||||
|
_ = Lexer;
|
||||||
|
_ = Parser;
|
||||||
|
}
|
||||||
|
|||||||
453
src/app.zig
453
src/app.zig
@ -7,7 +7,7 @@ const STBImage = @import("stb_image");
|
|||||||
const STBTrueType = @import("stb_truetype");
|
const STBTrueType = @import("stb_truetype");
|
||||||
const Tiled = @import("tiled");
|
const Tiled = @import("tiled");
|
||||||
|
|
||||||
const Platform = @import("platform/root.zig");
|
const Platform = @import("platform");
|
||||||
const Gfx = Platform.Gfx;
|
const Gfx = Platform.Gfx;
|
||||||
const Vec2 = Platform.Vec2;
|
const Vec2 = Platform.Vec2;
|
||||||
const Audio = Platform.Audio;
|
const Audio = Platform.Audio;
|
||||||
@ -47,11 +47,11 @@ const PlayerSprites = struct {
|
|||||||
const Tile = struct {
|
const Tile = struct {
|
||||||
ground_sprite: ?Gfx.Sprite.Id,
|
ground_sprite: ?Gfx.Sprite.Id,
|
||||||
|
|
||||||
entity: Entity.Id,
|
entities_by_layer: std.EnumArray(Entity.Layer, Entity.Id),
|
||||||
|
|
||||||
const empty = Tile{
|
const empty = Tile{
|
||||||
.ground_sprite = null,
|
.ground_sprite = null,
|
||||||
.entity = .nil
|
.entities_by_layer = .initFill(.nil)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -71,6 +71,17 @@ const Tilemap = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clone(self: Tilemap, gpa: Allocator) !Tilemap {
|
||||||
|
const tiles = try gpa.dupe(Tile, self.tiles);
|
||||||
|
errdefer gpa.free(tiles);
|
||||||
|
|
||||||
|
return Tilemap{
|
||||||
|
.tiles = tiles,
|
||||||
|
.width = self.width,
|
||||||
|
.height = self.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Tilemap, gpa: Allocator) void {
|
pub fn deinit(self: *Tilemap, gpa: Allocator) void {
|
||||||
gpa.free(self.tiles);
|
gpa.free(self.tiles);
|
||||||
}
|
}
|
||||||
@ -84,6 +95,7 @@ const Tilemap = struct {
|
|||||||
|
|
||||||
const Entity = struct {
|
const Entity = struct {
|
||||||
type: Type,
|
type: Type,
|
||||||
|
layer: Layer,
|
||||||
|
|
||||||
pos_x: i32 = -1,
|
pos_x: i32 = -1,
|
||||||
pos_y: i32 = -1,
|
pos_y: i32 = -1,
|
||||||
@ -96,9 +108,15 @@ const Entity = struct {
|
|||||||
const Type = enum {
|
const Type = enum {
|
||||||
player,
|
player,
|
||||||
box,
|
box,
|
||||||
|
goal_marker,
|
||||||
wall
|
wall
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Layer = enum {
|
||||||
|
ground,
|
||||||
|
surface,
|
||||||
|
};
|
||||||
|
|
||||||
const SlotMap = Platform.SlotMapType(u16, u8, Entity);
|
const SlotMap = Platform.SlotMapType(u16, u8, Entity);
|
||||||
const Id = SlotMap.Id;
|
const Id = SlotMap.Id;
|
||||||
};
|
};
|
||||||
@ -107,24 +125,45 @@ const World = struct {
|
|||||||
tilemap: Tilemap,
|
tilemap: Tilemap,
|
||||||
entities: Entity.SlotMap,
|
entities: Entity.SlotMap,
|
||||||
|
|
||||||
fn spawnPlayer(self: *World) !Entity.Id {
|
pub fn clone(self: World, gpa: Allocator) !World {
|
||||||
return try self.entities.insert(Entity{
|
var tilemap = try self.tilemap.clone(gpa);
|
||||||
.type = .player,
|
errdefer tilemap.deinit(gpa);
|
||||||
});
|
|
||||||
|
var entity_slots = try self.entities.slots.clone(gpa);
|
||||||
|
errdefer entity_slots.deinit(gpa);
|
||||||
|
|
||||||
|
var entities = self.entities;
|
||||||
|
entities.slots = entity_slots;
|
||||||
|
|
||||||
|
return World{
|
||||||
|
.tilemap = tilemap,
|
||||||
|
.entities = entities
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawnBox(self: *World, sprite: Gfx.Sprite.Id) !Entity.Id {
|
pub fn deinit(self: *World, gpa: Allocator) void {
|
||||||
return try self.entities.insert(Entity{
|
self.tilemap.deinit(gpa);
|
||||||
.type = .box,
|
self.entities.deinit(gpa);
|
||||||
.sprite = sprite
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawnWall(self: *World, sprite: Gfx.Sprite.Id) !Entity.Id {
|
fn spawnEntity(self: *World, opts: Entity) Entity.Id {
|
||||||
return try self.entities.insert(Entity{
|
var e = opts;
|
||||||
.type = .wall,
|
e.pos_x = -1;
|
||||||
.sprite = sprite
|
e.pos_y = -1;
|
||||||
});
|
const entity_id = self.entities.insert(e) catch |err| {
|
||||||
|
log.err("Failed to spawn entity: {}", .{err});
|
||||||
|
return .nil;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (opts.pos_x != -1 or opts.pos_y != -1) {
|
||||||
|
assert(opts.pos_x != -1 and opts.pos_y != -1);
|
||||||
|
if (!self.setEntityPosition(entity_id, opts.pos_x, opts.pos_y)) {
|
||||||
|
log.err("Entity position: {}, {}", .{opts.pos_x, opts.pos_y});
|
||||||
|
@panic("Failed to set initial entity position");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getTile(self: *World, x: i32, y: i32) ?*Tile {
|
fn getTile(self: *World, x: i32, y: i32) ?*Tile {
|
||||||
@ -136,18 +175,18 @@ const World = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn moveEntity(self: *World, id: Entity.Id, x: i32, y: i32) bool {
|
fn setEntityPosition(self: *World, id: Entity.Id, x: i32, y: i32) bool {
|
||||||
const e = self.entities.get(id) orelse return false;
|
const e = self.entities.get(id) orelse return false;
|
||||||
const destination_tile = self.getTile(x, y) orelse return false;
|
const destination_tile = self.getTile(x, y) orelse return false;
|
||||||
|
|
||||||
if (self.entities.exists(destination_tile.entity)) {
|
if (self.entities.exists(destination_tile.entities_by_layer.get(e.layer))) {
|
||||||
// Cna't overwrite entity
|
// Cant't overwrite entity
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
destination_tile.entity = id;
|
destination_tile.entities_by_layer.set(e.layer, id);
|
||||||
|
|
||||||
if (self.getTile(e.pos_x, e.pos_y)) |tile| {
|
if (self.getTile(e.pos_x, e.pos_y)) |tile| {
|
||||||
tile.entity = .nil;
|
tile.entities_by_layer.set(e.layer, .nil);
|
||||||
}
|
}
|
||||||
|
|
||||||
e.pos_x = x;
|
e.pos_x = x;
|
||||||
@ -156,20 +195,37 @@ const World = struct {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn movePlayer(self: *World, id: Entity.Id, dir_x: i32, dir_y: i32) bool {
|
fn moveEntity(self: *World, id: Entity.Id, dx: i32, dy: i32) bool {
|
||||||
const e = self.entities.get(id) orelse return false;
|
const e = self.entities.get(id) orelse return false;
|
||||||
|
|
||||||
const next_x = e.pos_x+dir_x;
|
const next_x = e.pos_x+dx;
|
||||||
const next_y = e.pos_y+dir_y;
|
const next_y = e.pos_y+dy;
|
||||||
if (!self.moveEntity(id, next_x, next_y)) {
|
|
||||||
return false;
|
const destination_tile = self.getTile(next_x, next_y) orelse return false;
|
||||||
|
|
||||||
|
if (e.type == .player or e.type == .box) {
|
||||||
|
const destination_entity_id = destination_tile.entities_by_layer.get(e.layer);
|
||||||
|
if (self.entities.get(destination_entity_id)) |destination_entity| {
|
||||||
|
if (destination_entity.type == .box) {
|
||||||
|
if (!self.moveEntity(destination_entity_id, dx, dy)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return self.setEntityPosition(id, next_x, next_y);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const Level = struct {
|
||||||
|
number: i32,
|
||||||
|
world: World,
|
||||||
|
};
|
||||||
|
|
||||||
world: World,
|
world: World,
|
||||||
|
current_level: u32 = 0,
|
||||||
|
levels: std.ArrayList(Level),
|
||||||
|
|
||||||
roboto_font: Font,
|
roboto_font: Font,
|
||||||
tilesheet: Tilesheet,
|
tilesheet: Tilesheet,
|
||||||
@ -246,80 +302,170 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
|||||||
.tilesheet = tilesheet,
|
.tilesheet = tilesheet,
|
||||||
.footstep_buffer = footstep_sound,
|
.footstep_buffer = footstep_sound,
|
||||||
.sfx_bus = Audio.addBus(.{ .label = "sfx" }),
|
.sfx_bus = Audio.addBus(.{ .label = "sfx" }),
|
||||||
.world = .{
|
.world = undefined,
|
||||||
.tilemap = undefined,
|
.levels = .empty,
|
||||||
.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);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn loadLevels(self: *App, gpa: Allocator, assets: *Platform.Assets) !std.ArrayList(Level) {
|
||||||
|
var xml_parser = try Tiled.xml.Parser.initCapacity(gpa, .{});
|
||||||
|
defer xml_parser.deinit(gpa);
|
||||||
|
|
||||||
|
const tilemap = try Tiled.Tilemap.initFromXml(
|
||||||
|
gpa,
|
||||||
|
try xml_parser.parse(assets.readFile("tiled/map.tmx")),
|
||||||
|
.root
|
||||||
|
);
|
||||||
|
defer tilemap.deinit();
|
||||||
|
|
||||||
|
var tilesets: Tiled.Tileset.List = .empty;
|
||||||
|
defer tilesets.deinit(gpa);
|
||||||
|
|
||||||
|
try tilesets.add(gpa, "tileset.tsx", try Tiled.Tileset.initFromXml(
|
||||||
|
gpa,
|
||||||
|
try xml_parser.parse(assets.readFile("tiled/tileset.tsx")),
|
||||||
|
.root
|
||||||
|
));
|
||||||
|
|
||||||
|
var levels: std.ArrayList(Level) = .empty;
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var player_pos: Vec2 = .zero;
|
std.mem.sort(Level, levels.items, Context{}, Context.lessThan);
|
||||||
{
|
|
||||||
var scratch = std.heap.ArenaAllocator.init(plt.gpa);
|
|
||||||
defer scratch.deinit();
|
|
||||||
|
|
||||||
var buffers = Tiled.xml.Lexer.Buffers.init(plt.gpa);
|
|
||||||
defer buffers.deinit();
|
|
||||||
|
|
||||||
const tilemap = try Tiled.Tilemap.initFromBuffer(plt.gpa, &scratch, &buffers, plt.assets.readFile("tiled/map.tmx"));
|
return levels;
|
||||||
defer tilemap.deinit();
|
}
|
||||||
|
|
||||||
var tilesets: Tiled.Tileset.List = .empty;
|
fn loadLevel(
|
||||||
defer tilesets.deinit(plt.gpa);
|
self: *App,
|
||||||
|
gpa: Allocator,
|
||||||
try tilesets.add(plt.gpa, "tileset.tsx", try Tiled.Tileset.initFromBuffer(plt.gpa, &scratch, &buffers, plt.assets.readFile("tiled/tileset.tsx")));
|
tilemap: Tiled.Tilemap,
|
||||||
|
tilesets: Tiled.Tileset.List,
|
||||||
const bounds = tilemap.getTileBounds();
|
bounds: Tiled.Tilemap.Bounds
|
||||||
|
) !Level {
|
||||||
self.world.tilemap = try Tilemap.init(plt.gpa, bounds.width(), bounds.height());
|
var level = Level{
|
||||||
|
.number = 0,
|
||||||
const ground = tilemap.getLayer("Ground").?;
|
.world = .{
|
||||||
const walls = tilemap.getLayer("Walls").?;
|
.tilemap = try .init(gpa, bounds.width(), bounds.height()),
|
||||||
for (0..bounds.height()) |oy| {
|
.entities = try .initCapacity(gpa, 256)
|
||||||
for (0..bounds.width()) |ox| {
|
|
||||||
const x = bounds.min_x + ox;
|
|
||||||
const y = bounds.min_y + 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 = tilesheet.get(pos.x, pos.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tilemap.getTile(walls, tilesets, x, y)) |tiled_tile| {
|
|
||||||
const pos = tiled_tile.getPosition();
|
|
||||||
const wall_id = try self.world.spawnWall(tilesheet.get(pos.x, pos.y));
|
|
||||||
_ = self.world.moveEntity(wall_id, @intCast(ox), @intCast(oy));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const markers = &tilemap.getLayer("Markers").?.variant.object;
|
const ground = tilemap.getLayer("Ground").?;
|
||||||
const spawn = markers.getByName("spawn").?;
|
const foreground = tilemap.getLayer("Foreground").?;
|
||||||
player_pos.x = @divFloor(spawn.shape.point.x, @as(f32, @floatFromInt(tilemap.tile_width)));
|
for (0..bounds.height()) |oy| {
|
||||||
player_pos.x -= @floatFromInt(bounds.min_x);
|
for (0..bounds.width()) |ox| {
|
||||||
player_pos.y = @divFloor(spawn.shape.point.y, @as(f32, @floatFromInt(tilemap.tile_height)));
|
const x = bounds.min_x + @as(i32, @intCast(ox));
|
||||||
player_pos.y -= @floatFromInt(bounds.min_y);
|
const y = bounds.min_y + @as(i32, @intCast(oy));
|
||||||
|
|
||||||
const boxes = tilemap.getLayer("Boxes").?;
|
const tile = level.world.tilemap.get(ox, oy);
|
||||||
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| {
|
if (tilemap.getTile(ground, tilesets, x, y)) |tiled_tile| {
|
||||||
const pos = tiled_tile.getPosition();
|
const pos = tiled_tile.getPosition();
|
||||||
const box_id = try self.world.spawnBox(tilesheet.get(pos.x, pos.y));
|
tile.ground_sprite = self.tilesheet.get(pos.x, pos.y);
|
||||||
_ = self.world.moveEntity(box_id, @intCast(ox), @intCast(oy));
|
}
|
||||||
|
|
||||||
|
if (tilemap.getTile(foreground, 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});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity_type != null) {
|
||||||
|
_ = level.world.spawnEntity(Entity{
|
||||||
|
.type = entity_type.?,
|
||||||
|
.layer = layer,
|
||||||
|
.pos_x = @intCast(ox),
|
||||||
|
.pos_y = @intCast(oy),
|
||||||
|
.sprite = self.tilesheet.get(pos.x, pos.y)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const player_id = try self.world.spawnPlayer();
|
for (tilemap.getLayer("Markers").?.variant.object.items) |obj| {
|
||||||
_ = self.world.moveEntity(player_id, @intFromFloat(player_pos.x), @intFromFloat(player_pos.y));
|
if (obj.shape != .point) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!std.mem.eql(u8, obj.name, "spawn")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
var position = obj.shape.point;
|
||||||
|
position.x /= @floatFromInt(tilemap.tile_width);
|
||||||
|
position.y /= @floatFromInt(tilemap.tile_height);
|
||||||
|
|
||||||
|
const pos_x: i32 = @intFromFloat(position.x);
|
||||||
|
const pos_y: i32 = @intFromFloat(position.y);
|
||||||
|
|
||||||
|
if (bounds.isInside(pos_x, pos_y)) {
|
||||||
|
_ = level.world.spawnEntity(Entity{
|
||||||
|
.type = .player,
|
||||||
|
.layer = .surface,
|
||||||
|
.pos_x = pos_x - bounds.min_x,
|
||||||
|
.pos_y = pos_y - bounds.min_y
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateWorld(world: *World) void {
|
||||||
|
_ = world; // autofix
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn frame(self: *App, plt: Platform.Frame) !void {
|
pub fn frame(self: *App, plt: Platform.Frame) !void {
|
||||||
@ -354,6 +500,63 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
|||||||
canvas.push();
|
canvas.push();
|
||||||
defer canvas.pop(clear_color);
|
defer canvas.pop(clear_color);
|
||||||
|
|
||||||
|
var goal_count: u32 = 0;
|
||||||
|
var box_at_goal_count: u32 = 0;
|
||||||
|
{
|
||||||
|
var entity_iter = self.world.entities.iterator();
|
||||||
|
while (entity_iter.next()) |entity_id| {
|
||||||
|
const entity = self.world.entities.getAssumeExists(entity_id);
|
||||||
|
if (entity.type == .player) {
|
||||||
|
if ((dir_x != 0 or dir_y != 0) and self.world.moveEntity(entity_id, dir_x, dir_y)) {
|
||||||
|
var new_direction = entity.player_direction;
|
||||||
|
if (dir_x < 0) {
|
||||||
|
new_direction = .left;
|
||||||
|
} else if (dir_x > 0) {
|
||||||
|
new_direction = .right;
|
||||||
|
} else if (dir_y > 0) {
|
||||||
|
new_direction = .down;
|
||||||
|
} else if (dir_y < 0) {
|
||||||
|
new_direction = .up;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entity.player_direction != new_direction) {
|
||||||
|
entity.player_frame_index = 0;
|
||||||
|
entity.player_direction = new_direction;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.footstep_sound = Audio.play(.{ .buffer = self.footstep_buffer, .bus = self.sfx_bus });
|
||||||
|
}
|
||||||
|
|
||||||
|
const frames = self.player_sprites.getDirection(entity.player_direction);
|
||||||
|
|
||||||
|
if (dir_x != 0 or dir_y != 0) {
|
||||||
|
self.animation_timer += plt.input.delta_time;
|
||||||
|
const animatin_time = std.time.ns_per_s / 5;
|
||||||
|
while (self.animation_timer >= animatin_time) {
|
||||||
|
entity.player_frame_index = (entity.player_frame_index + 1) % @as(u32, @intCast(frames.len));
|
||||||
|
self.animation_timer -= animatin_time;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.animation_timer = 0;
|
||||||
|
entity.player_frame_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.height) |y| {
|
||||||
for (0..self.world.tilemap.width) |x| {
|
for (0..self.world.tilemap.width) |x| {
|
||||||
const tile = self.world.tilemap.get(x, y);
|
const tile = self.world.tilemap.get(x, y);
|
||||||
@ -364,70 +567,40 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var entity_iter = self.world.entities.iterator();
|
inline for (.{ Entity.Layer.ground, Entity.Layer.surface }) |layer| {
|
||||||
while (entity_iter.next()) |entity_id| {
|
var entity_iter = self.world.entities.iterator();
|
||||||
const entity = self.world.entities.getAssumeExists(entity_id);
|
while (entity_iter.next()) |entity_id| {
|
||||||
if (entity.type == .player) {
|
const entity = self.world.entities.getAssumeExists(entity_id);
|
||||||
if ((dir_x != 0 or dir_y != 0) and self.world.movePlayer(entity_id, dir_x, dir_y)) {
|
if (entity.layer != layer) {
|
||||||
var new_direction = entity.player_direction;
|
continue;
|
||||||
if (dir_x < 0) {
|
|
||||||
new_direction = .left;
|
|
||||||
} else if (dir_x > 0) {
|
|
||||||
new_direction = .right;
|
|
||||||
} else if (dir_y > 0) {
|
|
||||||
new_direction = .down;
|
|
||||||
} else if (dir_y < 0) {
|
|
||||||
new_direction = .up;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entity.player_direction != new_direction) {
|
|
||||||
entity.player_frame_index = 0;
|
|
||||||
entity.player_direction = new_direction;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.footstep_sound = Audio.play(.{ .buffer = self.footstep_buffer, .bus = self.sfx_bus });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const frames = self.player_sprites.getDirection(entity.player_direction);
|
if (entity.sprite) |sprite| {
|
||||||
|
Gfx.drawSprite(
|
||||||
if (dir_x != 0 or dir_y != 0) {
|
sprite,
|
||||||
self.animation_timer += plt.input.delta_time;
|
entity.visual_pos.multiply(tile_size),
|
||||||
const animatin_time = std.time.ns_per_s / 5;
|
tile_size,
|
||||||
while (self.animation_timer >= animatin_time) {
|
.white
|
||||||
entity.player_frame_index = (entity.player_frame_index + 1) % @as(u32, @intCast(frames.len));
|
);
|
||||||
self.animation_timer -= animatin_time;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.animation_timer = 0;
|
|
||||||
entity.player_frame_index = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entity.sprite = frames[entity.player_frame_index];
|
|
||||||
}
|
|
||||||
|
|
||||||
entity.visual_pos = .initFromInt(i32, entity.pos_x, entity.pos_y);
|
|
||||||
|
|
||||||
if (entity.sprite) |sprite| {
|
|
||||||
Gfx.drawSprite(
|
|
||||||
sprite,
|
|
||||||
entity.visual_pos.multiply(tile_size),
|
|
||||||
tile_size,
|
|
||||||
.white
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// self.roboto_font.drawText(.{
|
if (goal_count > 0 and goal_count == box_at_goal_count and self.current_level+1 < self.levels.items.len) {
|
||||||
// .text = try std.fmt.allocPrint(plt.frame, "Hello {:.0}!", .{plt.time()}),
|
self.current_level += 1;
|
||||||
// .pos = .init(0, 0),
|
self.world.deinit(plt.gpa);
|
||||||
// .height = 32,
|
self.world = try self.levels.items[self.current_level].world.clone(plt.gpa);
|
||||||
// });
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *App, plt: Platform.Deinit) void {
|
pub fn deinit(self: *App, plt: Platform.Deinit) void {
|
||||||
self.roboto_font.deinit(plt.gpa);
|
self.roboto_font.deinit(plt.gpa);
|
||||||
Gfx.deinitSpritesheet(self.roboto_font.spritesheet);
|
Gfx.deinitSpritesheet(self.roboto_font.spritesheet);
|
||||||
|
|
||||||
self.world.tilemap.deinit(plt.gpa);
|
self.world.deinit(plt.gpa);
|
||||||
self.world.entities.slots.deinit(plt.gpa);
|
|
||||||
|
for (self.levels.items) |*level| {
|
||||||
|
level.world.deinit(plt.gpa);
|
||||||
|
}
|
||||||
|
self.levels.deinit(plt.gpa);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
const Math = @import("math");
|
const Platform = @import("platform");
|
||||||
const Vec2 = Math.Vec2;
|
|
||||||
|
|
||||||
const Platform = @import("platform/root.zig");
|
|
||||||
const Gfx = Platform.Gfx;
|
const Gfx = Platform.Gfx;
|
||||||
const Color = Platform.Color;
|
const Color = Platform.Color;
|
||||||
|
|
||||||
|
const Vec2 = Platform.Math.Vec2;
|
||||||
|
|
||||||
const Canvas = @This();
|
const Canvas = @This();
|
||||||
|
|
||||||
size: Vec2,
|
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 Allocator = std.mem.Allocator;
|
||||||
const Io = std.Io;
|
const Io = std.Io;
|
||||||
|
|
||||||
const Platform = @import("platform/root.zig");
|
const Platform = @import("platform");
|
||||||
const Gfx = Platform.Gfx;
|
const Gfx = Platform.Gfx;
|
||||||
const SlotMapType = Platform.SlotMapType;
|
const SlotMapType = Platform.SlotMapType;
|
||||||
const Vec2 = Platform.Vec2;
|
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);
|
assert(box_height < Font.Glyph.max_height);
|
||||||
|
|
||||||
var bitmap_buffer: [Font.Glyph.max_width * Font.Glyph.max_height]u8 = undefined;
|
var bitmap_buffer: [Font.Glyph.max_width * Font.Glyph.max_height]u8 = undefined;
|
||||||
const bitmap = Gfx.ImageData{
|
const bitmap = Gfx.Image{
|
||||||
.pixels = .{ .r8 = (&bitmap_buffer).ptr },
|
.pixels = .{ .r8 = (&bitmap_buffer).ptr },
|
||||||
.width = box_width,
|
.width = box_width,
|
||||||
.height = box_height
|
.height = box_height
|
||||||
@ -418,8 +418,8 @@ const GlyphCache = struct {
|
|||||||
defer cache.deinit(gpa);
|
defer cache.deinit(gpa);
|
||||||
|
|
||||||
const key = Glyph.Key{ .index = 10, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
|
const key = Glyph.Key{ .index = 10, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
|
||||||
const id1 = cache.insert(key).?;
|
const id1 = cache.insertOrGet(key).?.id;
|
||||||
const id2 = cache.insert(key).?;
|
const id2 = cache.insertOrGet(key).?.id;
|
||||||
try std.testing.expectEqual(id1, id2);
|
try std.testing.expectEqual(id1, id2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,9 +429,9 @@ const GlyphCache = struct {
|
|||||||
defer cache.deinit(gpa);
|
defer cache.deinit(gpa);
|
||||||
|
|
||||||
const key = Glyph.Key{ .index = 10, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
|
const key = Glyph.Key{ .index = 10, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
|
||||||
const id1 = cache.insert(key).?;
|
const id1 = cache.insertOrGet(key).?.id;
|
||||||
cache.remove(id1);
|
cache.remove(id1);
|
||||||
const id2 = cache.insert(key).?;
|
const id2 = cache.insertOrGet(key).?.id;
|
||||||
try std.testing.expectEqual(id1, id2);
|
try std.testing.expectEqual(id1, id2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -440,8 +440,8 @@ const GlyphCache = struct {
|
|||||||
var cache = try GlyphCache.init(gpa, 256);
|
var cache = try GlyphCache.init(gpa, 256);
|
||||||
defer cache.deinit(gpa);
|
defer cache.deinit(gpa);
|
||||||
|
|
||||||
const id1 = cache.insert(Glyph.Key{ .index = 10, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
|
const id1 = cache.insertOrGet(Glyph.Key{ .index = 10, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
|
||||||
const id2 = cache.insert(Glyph.Key{ .index = 11, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
|
const id2 = cache.insertOrGet(Glyph.Key{ .index = 11, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
|
||||||
try std.testing.expect(id1 != id2);
|
try std.testing.expect(id1 != id2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -452,10 +452,10 @@ const GlyphCache = struct {
|
|||||||
defer cache.deinit(gpa);
|
defer cache.deinit(gpa);
|
||||||
|
|
||||||
for (0..limit-1) |i| {
|
for (0..limit-1) |i| {
|
||||||
const id = cache.insert(Glyph.Key{ .index = @intCast(i), .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
|
const id = cache.insertOrGet(Glyph.Key{ .index = @intCast(i), .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
|
||||||
try std.testing.expect(id != null);
|
try std.testing.expect(id != null);
|
||||||
}
|
}
|
||||||
const id = cache.insert(Glyph.Key{ .index = limit, .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
|
const id = cache.insertOrGet(Glyph.Key{ .index = limit, .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
|
||||||
try std.testing.expect(id == null);
|
try std.testing.expect(id == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -470,9 +470,9 @@ const GlyphCache = struct {
|
|||||||
defer ids.deinit(gpa);
|
defer ids.deinit(gpa);
|
||||||
|
|
||||||
for (0..(limit-1)) |i| {
|
for (0..(limit-1)) |i| {
|
||||||
const id = cache.insert(Glyph.Key{ .index = @intCast(i), .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
|
const id = cache.insertOrGet(Glyph.Key{ .index = @intCast(i), .scale_x = .fromF32(1), .scale_y = .fromF32(1) });
|
||||||
try std.testing.expect(id != null);
|
try std.testing.expect(id != null);
|
||||||
try ids.append(gpa, id.?);
|
try ids.append(gpa, id.?.id);
|
||||||
}
|
}
|
||||||
assert(cache.free_head == GlyphSlot.nil_id);
|
assert(cache.free_head == GlyphSlot.nil_id);
|
||||||
|
|
||||||
@ -498,9 +498,9 @@ const GlyphCache = struct {
|
|||||||
var cache = try GlyphCache.init(gpa, 256);
|
var cache = try GlyphCache.init(gpa, 256);
|
||||||
defer cache.deinit(gpa);
|
defer cache.deinit(gpa);
|
||||||
|
|
||||||
const id1 = cache.insert(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
|
const id1 = cache.insertOrGet(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
|
||||||
const id2 = cache.insert(.{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
|
const id2 = cache.insertOrGet(.{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
|
||||||
const id3 = cache.insert(.{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
|
const id3 = cache.insertOrGet(.{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
|
||||||
|
|
||||||
const lru = try cache.getLRUItems(gpa);
|
const lru = try cache.getLRUItems(gpa);
|
||||||
defer gpa.free(lru);
|
defer gpa.free(lru);
|
||||||
@ -513,9 +513,9 @@ const GlyphCache = struct {
|
|||||||
var cache = try GlyphCache.init(gpa, 256);
|
var cache = try GlyphCache.init(gpa, 256);
|
||||||
defer cache.deinit(gpa);
|
defer cache.deinit(gpa);
|
||||||
|
|
||||||
const id1 = cache.insert(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
|
const id1 = cache.insertOrGet(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
|
||||||
const id2 = cache.insert(.{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
|
const id2 = cache.insertOrGet(.{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
|
||||||
const id3 = cache.insert(.{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
|
const id3 = cache.insertOrGet(.{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
|
||||||
|
|
||||||
const lru = try cache.getLRUItems(gpa);
|
const lru = try cache.getLRUItems(gpa);
|
||||||
defer gpa.free(lru);
|
defer gpa.free(lru);
|
||||||
@ -528,7 +528,7 @@ const GlyphCache = struct {
|
|||||||
var cache = try GlyphCache.init(gpa, 256);
|
var cache = try GlyphCache.init(gpa, 256);
|
||||||
defer cache.deinit(gpa);
|
defer cache.deinit(gpa);
|
||||||
|
|
||||||
const id = cache.insert(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
|
const id = cache.insertOrGet(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
|
||||||
try std.testing.expect(cache.lru_head != GlyphSlot.nil_id);
|
try std.testing.expect(cache.lru_head != GlyphSlot.nil_id);
|
||||||
try std.testing.expect(cache.lru_tail != GlyphSlot.nil_id);
|
try std.testing.expect(cache.lru_tail != GlyphSlot.nil_id);
|
||||||
cache.remove(id);
|
cache.remove(id);
|
||||||
@ -542,13 +542,13 @@ const GlyphCache = struct {
|
|||||||
defer cache.deinit(gpa);
|
defer cache.deinit(gpa);
|
||||||
|
|
||||||
const key1 = Glyph.Key{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
|
const key1 = Glyph.Key{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
|
||||||
const id1 = cache.insert(key1).?;
|
const id1 = cache.insertOrGet(key1).?.id;
|
||||||
const key2 = Glyph.Key{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
|
const key2 = Glyph.Key{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
|
||||||
const id2 = cache.insert(key2).?;
|
const id2 = cache.insertOrGet(key2).?.id;
|
||||||
const key3 = Glyph.Key{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
|
const key3 = Glyph.Key{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) };
|
||||||
const id3 = cache.insert(key3).?;
|
const id3 = cache.insertOrGet(key3).?.id;
|
||||||
|
|
||||||
_ = cache.lookup(key2);
|
_ = cache.insertOrGet(key2);
|
||||||
|
|
||||||
{
|
{
|
||||||
const lru = try cache.getLRUItems(gpa);
|
const lru = try cache.getLRUItems(gpa);
|
||||||
@ -556,7 +556,7 @@ const GlyphCache = struct {
|
|||||||
try std.testing.expectEqualSlices(GlyphSlot.Id, &.{ id2, id3, id1 }, lru);
|
try std.testing.expectEqualSlices(GlyphSlot.Id, &.{ id2, id3, id1 }, lru);
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = cache.lookup(key1);
|
_ = cache.insertOrGet(key1);
|
||||||
|
|
||||||
{
|
{
|
||||||
const lru = try cache.getLRUItems(gpa);
|
const lru = try cache.getLRUItems(gpa);
|
||||||
@ -570,9 +570,9 @@ const GlyphCache = struct {
|
|||||||
var cache = try GlyphCache.init(gpa, 256);
|
var cache = try GlyphCache.init(gpa, 256);
|
||||||
defer cache.deinit(gpa);
|
defer cache.deinit(gpa);
|
||||||
|
|
||||||
const id1 = cache.insert(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
|
const id1 = cache.insertOrGet(.{ .index = 0, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
|
||||||
const id2 = cache.insert(.{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
|
const id2 = cache.insertOrGet(.{ .index = 1, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
|
||||||
const id3 = cache.insert(.{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?;
|
const id3 = cache.insertOrGet(.{ .index = 2, .scale_x = .fromF32(1), .scale_y = .fromF32(1) }).?.id;
|
||||||
|
|
||||||
{
|
{
|
||||||
const lru = try cache.getLRUItems(gpa);
|
const lru = try cache.getLRUItems(gpa);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const Platform = @import("platform/root.zig");
|
const Platform = @import("platform");
|
||||||
const App = @import("app.zig");
|
const App = @import("app.zig");
|
||||||
|
|
||||||
pub const std_options: std.Options = .{
|
pub const std_options: std.Options = .{
|
||||||
@ -21,5 +21,5 @@ pub fn main(init: std.process.Init) void {
|
|||||||
|
|
||||||
test {
|
test {
|
||||||
std.testing.refAllDecls(@This());
|
std.testing.refAllDecls(@This());
|
||||||
_ = @import("./platform/slot_map.zig");
|
_ = Platform;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,6 @@ const Mat4 = Math.Mat4;
|
|||||||
const Rect = Math.Rect;
|
const Rect = Math.Rect;
|
||||||
|
|
||||||
const STBRectPack = @import("stb_rect_pack");
|
const STBRectPack = @import("stb_rect_pack");
|
||||||
const STBTrueType = @import("stb_truetype");
|
|
||||||
const ImGUI = @import("imgui.zig");
|
const ImGUI = @import("imgui.zig");
|
||||||
pub const Color = @import("./color.zig");
|
pub const Color = @import("./color.zig");
|
||||||
const shd = @import("shader");
|
const shd = @import("shader");
|
||||||
@ -126,7 +125,7 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
|||||||
|
|
||||||
bindings.vertex_buffers[0] = sg.makeBuffer(.{
|
bindings.vertex_buffers[0] = sg.makeBuffer(.{
|
||||||
.size = @sizeOf(VertexQuad) * max_quads,
|
.size = @sizeOf(VertexQuad) * max_quads,
|
||||||
.usage = .{ .vertex_buffer = true, .stream_update = true },
|
.usage = .{ .vertex_buffer = true, .dynamic_update = true },
|
||||||
.label = "quad-vertices"
|
.label = "quad-vertices"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ const std = @import("std");
|
|||||||
const log = std.log.scoped(.app);
|
const log = std.log.scoped(.app);
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const Platform = @import("platform/root.zig");
|
const Platform = @import("platform");
|
||||||
const Gfx = Platform.Gfx;
|
const Gfx = Platform.Gfx;
|
||||||
const Vec2 = Platform.Vec2;
|
const Vec2 = Platform.Vec2;
|
||||||
const Rect = Platform.Rect;
|
const Rect = Platform.Rect;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user