Parse ldtk file without error

dev
Louis Pearson 2022-08-02 23:15:51 -06:00
parent c9daf228ad
commit c8c86dd11d
4 changed files with 289 additions and 132 deletions

View File

@ -1,57 +1,50 @@
const std = @import("std"); const std = @import("std");
pub fn parse(allocator: std.mem.Allocator, json_string: []const u8) !Root { // pub fn parse(parser: *std.json.Parser, json_string: []const u8) !std.json.Value {
@setEvalBranchQuota(10_0000); // const value_tree = try parser.parse(json_string);
var tokens = std.json.TokenStream.init(json_string); // value_tree.root.dump();
const root = try std.json.parse(Root, &tokens, .{ .allocator = allocator });
return root;
}
pub fn parseFree(allocator: std.mem.Allocator, root: Root) void { // const root_obj = switch (value_tree.root) {
std.json.parseFree(Root, root, .{ .allocator = allocator }); // .Object => |obj| obj,
} // else => return error.InvalidRoot,
// };
// _ = root_obj;
// // const root = Root {
// // name: root_obj.get("name") orelse error.InvalidRoot,
// // version: root_obj.get("jsonVersion") orelse error.InvalidRoot,
// // defaultPivotX: root_obj.get("defaultPivotX") orelse error.InvalidRoot,
// // defaultPivotY: root_obj.get("defaultPivotY") orelse error.InvalidRoot,
// // defaultGridSize: root_obj.get("defaultGridSize") orelse error.InvalidRoot,
// // name: root_obj.get("name") orelse error.InvalidRoot,
// // };
// return Root{};
// }
/// 1. LDtk Json root /// 1. LDtk Json root
const Root = struct { pub const Root = struct {
__header__: __Header__,
name: []const u8,
jsonVersion: u64,
defaultPivotX: f64,
defaultPivotY: f64,
defaultGridSize: u64,
bgColor: []const u8, bgColor: []const u8,
nextUid: u64, defs: ?Definitions = null,
externalLevels: bool,
defs: Definitions, jsonVersion: []const u8,
levels: []Level, levels: []Level,
worldGridHeight: ?i64 = null,
worldGridHeight: ?u64 = null, worldGridWidth: ?i64 = null,
worldGridWidth: ?u64 = null,
worldLayout: ?WorldLayout = null, worldLayout: ?WorldLayout = null,
worlds: []World, worlds: ?[]World = null,
};
const __Header__ = struct {
fileType: []const u8,
app: []const u8,
appAuthor: []const u8,
appVersion: []const u8,
url: []const u8,
}; };
/// 1.1. World /// 1.1. World
const World = struct { pub const World = struct {
identifier: []const u8, identifier: []const u8,
iid: []const u8, iid: []const u8,
levels: []Level, levels: []Level,
worldGridHeight: u64, worldGridHeight: i64,
worldGridWidth: u64, worldGridWidth: i64,
worldLayout: WorldLayout, worldLayout: WorldLayout,
}; };
const WorldLayout = enum { pub const WorldLayout = enum {
Free, Free,
GridVania, GridVania,
LinearHorizontal, LinearHorizontal,
@ -59,59 +52,61 @@ const WorldLayout = enum {
}; };
/// 2. Level /// 2. Level
const Level = struct { pub const Level = struct {
__bgColor: []const u8, __bgColor: ?[]const u8,
__bgPos: ?struct { __bgPos: ?struct {
cropRect: [4]f64, cropRect: [4]f64,
scale: [2]f64, scale: [2]f64,
topLeftPx: [2]i64, topLeftPx: [2]i64,
}, },
__neighbours: []struct { __neighbours: []Neighbour,
dir: []const u8,
levelIid: []const u8,
levelUid: ?u64 = null,
},
bgRelPath: ?[]const u8, bgRelPath: ?[]const u8,
externalRelPath: ?[]const u8, externalRelPath: ?[]const u8,
fieldInstances: []FieldInstance, fieldInstances: []FieldInstance,
identifier: []const u8, identifier: []const u8,
iid: []const u8, iid: []const u8,
layerInstances: ?[]LayerInstance, layerInstances: ?[]LayerInstance,
pxHei: u64, pxHei: i64,
pxWid: u64, pxWid: i64,
uid: u64, uid: i64,
worldDepth: i64, worldDepth: i64,
worldX: i64, worldX: i64,
worldY: i64, worldY: i64,
}; };
pub const Neighbour = struct {
dir: []const u8,
levelIid: []const u8,
levelUid: ?i64 = null,
};
/// 2.1. Layer instance /// 2.1. Layer instance
const LayerInstance = struct { const LayerInstance = struct {
__cHei: u64, __cHei: i64,
__cWid: u64, __cWid: i64,
__gridSize: u64, __gridSize: i64,
__identifier: []const u8, __identifier: []const u8,
__opacity: f64, __opacity: f64,
__pxTotalOffsetX: i64, __pxTotalOffsetX: i64,
__pxTotalOffsetY: i64, __pxTotalOffsetY: i64,
__tilesetDefUid: ?u64, __tilesetDefUid: ?i64,
__tilesetRelPath: ?[]const u8, __tilesetRelPath: ?[]const u8,
__type: []const u8, __type: []const u8,
autoLayerTiles: []TileInstance, autoLayerTiles: []TileInstance,
entityInstances: []EntityInstance, entityInstances: []EntityInstance,
gridTiles: []TileInstance, gridTiles: []TileInstance,
iid: []const u8, iid: []const u8,
intGridCsv: []u64, intGridCsv: []i64,
layerDefUid: u64, layerDefUid: i64,
levelId: u64, levelId: i64,
overrideTilesetUid: ?u64, overrideTilesetUid: ?i64,
pxOffsetX: u64, pxOffsetX: i64,
pxOffsetY: u64, pxOffsetY: i64,
visible: bool, visible: bool,
/// WARNING: this deprecated value is no longer exported since version 1.0.0 /// WARNING: this deprecated value is no longer exported since version 1.0.0
/// Replaced by: intGridCsv /// Replaced by: intGridCsv
intGrid: ?[][]const u8 = null, intGrid: ?[][]const u8 = null,
// seed: u64, // seed: i64,
// autoTiles: []AutoTile, // autoTiles: []AutoTile,
}; };
@ -127,7 +122,7 @@ const TileInstance = struct {
f: FlipBits, f: FlipBits,
px: [2]i64, px: [2]i64,
src: [2]i64, src: [2]i64,
t: u64, t: i64,
}; };
const FlipBits = enum(u4) { const FlipBits = enum(u4) {
@ -145,23 +140,23 @@ const EntityInstance = struct {
__smartColor: []const u8, __smartColor: []const u8,
__tags: [][]const u8, __tags: [][]const u8,
__tile: ?TilesetRectangle, __tile: ?TilesetRectangle,
defUid: u64, defUid: i64,
fieldInstances: []FieldInstance, fieldInstances: []FieldInstance,
height: u64, height: i64,
iid: []const u8, iid: []const u8,
px: [2]i64, px: [2]i64,
width: u64, width: i64,
}; };
/// 2.4. Field Instance /// 2.4. Field Instance
const FieldInstance = struct { pub const FieldInstance = struct {
__identifier: []const u8, __identifier: []const u8,
__tile: ?TilesetRectangle, __tile: ?TilesetRectangle,
// TODO: type and value have many possible values and are not always strings. // TODO: type and value have many possible values and are not always strings.
// Figure out if we can use JSON.parse for this // Figure out if we can use JSON.parse for this
__type: []const u8, __type: []const u8,
__value: []const u8, __value: []const u8,
defUid: u64, defUid: i64,
}; };
const FieldType = union(enum) { const FieldType = union(enum) {
@ -209,11 +204,11 @@ const LayerDefinition = struct {
Tiles, Tiles,
AutoLayer, AutoLayer,
}, },
autoSourceLayerDefUid: ?u64, autoSourceLayerDefUid: ?i64,
displayOpacity: f64, displayOpacity: f64,
gridSize: u64, gridSize: i64,
identifier: []const u8, identifier: []const u8,
intGridValues: []struct { color: []const u8, identifier: ?[]const u8, value: u64 }, intGridValues: []struct { color: []const u8, identifier: ?[]const u8, value: i64 },
parallaxFactorX: f64, parallaxFactorX: f64,
parallaxFactorY: f64, parallaxFactorY: f64,
parallaxScaling: bool, parallaxScaling: bool,
@ -222,12 +217,12 @@ const LayerDefinition = struct {
/// Reference to the default Tileset UID used by this layer definition. /// Reference to the default Tileset UID used by this layer definition.
/// WARNING: some layer instances might use a different tileset. So most of the time, you should probably use the __tilesetDefUid value found in layer instances. /// WARNING: some layer instances might use a different tileset. So most of the time, you should probably use the __tilesetDefUid value found in layer instances.
/// NOTE: since version 1.0.0, the old autoTilesetDefUid was removed and merged into this value. /// NOTE: since version 1.0.0, the old autoTilesetDefUid was removed and merged into this value.
tilesetDefUid: ?u64, tilesetDefUid: ?i64,
/// Unique Int identifier /// Unique Int identifier
uid: u64, uid: i64,
/// WARNING: this deprecated value will be removed completely on version 1.2.0+ /// WARNING: this deprecated value will be removed completely on version 1.2.0+
/// Replaced by: tilesetDefUid /// Replaced by: tilesetDefUid
autoTilesetDefUid: ?u64 = null, autoTilesetDefUid: ?i64 = null,
}; };
/// 3.1.1. Auto-layer rule definition /// 3.1.1. Auto-layer rule definition
@ -236,19 +231,19 @@ const AutoLayerRuleDefinition = opaque {};
/// 3.2. Entity definition /// 3.2. Entity definition
const EntityDefinition = struct { const EntityDefinition = struct {
color: []const u8, color: []const u8,
height: u64, height: i64,
identifier: []const u8, identifier: []const u8,
nineSliceBorders: [4]i64, nineSliceBorders: [4]i64,
pivotX: f64, pivotX: f64,
pivotY: f64, pivotY: f64,
tileRect: TilesetRectangle, tileRect: TilesetRectangle,
tileRenderMode: enum { Cover, FitInside, Repeat, Stretch, FullSizeCropped, FullSizeUncropped, NineSlice }, tileRenderMode: enum { Cover, FitInside, Repeat, Stretch, FullSizeCropped, FullSizeUncropped, NineSlice },
tilesetId: ?u64, tilesetId: ?i64,
uid: u64, uid: i64,
width: u64, width: i64,
/// WARNING: this deprecated value will be removed completely on version 1.2.0+ /// WARNING: this deprecated value will be removed completely on version 1.2.0+
/// Replaced by tileRect /// Replaced by tileRect
tileId: ?u64 = null, tileId: ?i64 = null,
}; };
/// 3.2.1. Field definition /// 3.2.1. Field definition
@ -256,52 +251,52 @@ const FieldDefinition = []const u8;
/// 3.2.2. Tileset rectangle /// 3.2.2. Tileset rectangle
const TilesetRectangle = struct { const TilesetRectangle = struct {
h: u64, h: i64,
tilesetUid: u64, tilesetUid: i64,
w: u64, w: i64,
x: i64, x: i64,
y: i64, y: i64,
}; };
/// 3.3. Tileset definition /// 3.3. Tileset definition
const TilesetDefinition = struct { const TilesetDefinition = struct {
__cHei: u64, __cHei: i64,
__cWid: u64, __cWid: i64,
customData: []struct { customData: []struct {
data: []const u8, data: []const u8,
tileId: u64, tileId: i64,
}, },
embedAtlas: ?enum { LdtkIcons }, embedAtlas: ?enum { LdtkIcons },
enumTags: []struct { enumTags: []struct {
enumValueId: []const u8, enumValueId: []const u8,
tileIds: []u64, tileIds: []i64,
}, },
identifier: []const u8, identifier: []const u8,
padding: i64, padding: i64,
pxHei: u64, pxHei: i64,
pxWid: u64, pxWid: i64,
relPath: ?[]const u8, relPath: ?[]const u8,
spacing: i64, spacing: i64,
tags: [][]const u8, tags: [][]const u8,
tagsSourceEnumUid: ?u64, tagsSourceEnumUid: ?i64,
tileGridSize: u64, tileGridSize: i64,
uid: u64, uid: i64,
}; };
/// 3.4. Enum definition /// 3.4. Enum definition
const EnumDefinition = struct { const EnumDefinition = struct {
externalRelPath: ?[]const u8, externalRelPath: ?[]const u8,
iconTilesetUid: ?u64, iconTilesetUid: ?i64,
identifier: []const u8, identifier: []const u8,
tags: [][]const u8, tags: [][]const u8,
uid: u64, uid: i64,
values: []EnumValueDefinition, values: []EnumValueDefinition,
}; };
/// 3.4.1. Enum value definition /// 3.4.1. Enum value definition
const EnumValueDefinition = struct { const EnumValueDefinition = struct {
__tileSrcRect: ?[4]i64, __tileSrcRect: ?[4]i64,
color: u64, color: i64,
id: []const u8, id: []const u8,
tileId: ?u64, tileId: ?i64,
}; };

View File

@ -1,38 +0,0 @@
{
"__header__" : { // Some information header
"fileType": "LDtk Project JSON",
"app": "LDtk",
"appAuthor": "Sebastien Benard",
"appVersion": "0.6.3",
"url": "..."
},
// Global project settings
"name" : "My empty project",
"jsonVersion" : 1,
"defaultPivotX" : 0.0,
"defaultPivotY" : 0.0,
"defaultGridSize" : 16,
"bgColor" : "#7F8093",
"nextUid" : 1,
"defs" : { // These are elements definitions
"layers" : [],
"entities" : [],
"tilesets" : [],
"enums" : [],
"externalEnums" : []
},
"levels" : [ // Actual level data
{
"identifier" : "Level",
"uid" : 0,
"pxWid" : 256,
"pxHei" : 256,
"layerInstances" : [] // "null" if levels are separated
}
]
}

View File

@ -3,7 +3,144 @@ const testing = std.testing;
const LDtk = @import("LDtk.zig"); const LDtk = @import("LDtk.zig");
test "load default/empty ldtk file" { test "load default/empty ldtk file" {
const empty_ldtk = @embedFile("empty.ldtk"); const empty_ldtk = @embedFile("test.ldtk");
const world = try LDtk.parse(testing.allocator, empty_ldtk);
_ = world; var parser = std.json.Parser.init(testing.allocator, false);
defer parser.deinit();
var value_tree = try parser.parse(empty_ldtk);
defer value_tree.deinit();
value_tree.root.dump();
// Seperate root for easier access
const root = object(value_tree.root) orelse return error.InvalidRoot;
// Pull out the more complicated structures
// const defs = object(root.get("defs")) orelse return error.InvalidDefs;
const levels = array(root.get("levels")) orelse return error.InvalidLevels;
// const ldtk_defs = try extract_defs(testing.allocator, defs);
// defer testing.allocator.free(ldtk_defs);
const ldtk_levels = try extract_levels(testing.allocator, levels);
defer testing.allocator.free(ldtk_levels);
var ldtk_root = LDtk.Root {
.bgColor = string(root.get("bgColor")) orelse return error.InvalidBGColor,
// .defs = ldtk_defs,
.externalLevels = boolean(root.get("externalLevels")) orelse return error.InvalidExternalLevels,
.jsonVersion = string(root.get("jsonVersion")) orelse return error.InvalidJsonVersion,
.levels = ldtk_levels,
.worldGridHeight = integer(root.get("worldGridHeight")) orelse return error.InvalidHeight,
.worldGridWidth = integer(root.get("worldGridWidth")) orelse return error.InvalidWidth,
.worldLayout = enum_from_value(LDtk.WorldLayout, root.get("worldLayout")) orelse return error.InvalidWorldLayout,
};
if (array(root.get("worlds"))) |worlds| {
const ldtk_worlds = try extract_worlds(testing.allocator, worlds);
defer testing.allocator.free(ldtk_worlds);
ldtk_root.worlds = ldtk_worlds;
}
_ = ldtk_root;
std.log.warn("\n{?}\n", .{ ldtk_root });
}
// pub fn extract_defs(alloc: std.mem.Allocator, defs_obj: std.json.Value) !LDtk.Definitions {
// // TODO
// }
pub fn extract_worlds(alloc: std.mem.Allocator, worlds: std.json.Array) ![]LDtk.World {
var ldtk_worlds = try std.ArrayList(LDtk.World).initCapacity(alloc, worlds.items.len);
for (worlds.items) |world_value| {
const world_obj = object(world_value) orelse return error.InvalidWorld;
const levels_obj = array(world_obj.get("levels")) orelse return error.InvalidWorldLevels;
const levels = try extract_levels(alloc, levels_obj);
ldtk_worlds.appendAssumeCapacity(.{
.identifier = string(world_obj.get("identifier")) orelse return error.InvalidIdentifier,
.iid = string(world_obj.get("iid")) orelse return error.InvalidIID,
.levels = levels,
.worldGridHeight = integer(world_obj.get("worldGridHeight")) orelse return error.InvalidWorldGridHeight,
.worldGridWidth = integer(world_obj.get("worldGridHeight")) orelse return error.InvalidWorldGridHeight,
.worldLayout = enum_from_value(LDtk.WorldLayout, world_obj.get("worldLayout")) orelse return error.InvalidWorldLayout,
});
}
return ldtk_worlds.toOwnedSlice();
}
pub fn extract_levels(alloc: std.mem.Allocator, levels: std.json.Array) ![]LDtk.Level {
var ldtk_levels = try std.ArrayList(LDtk.Level).initCapacity(alloc, levels.items.len);
defer ldtk_levels.deinit(); // levels will be returned using toOwnedSlice
for (levels.items) |level_value| {
const level_obj = object(level_value) orelse return error.InvalidLevel;
ldtk_levels.appendAssumeCapacity(.{
.__bgColor = string(level_obj.get("__bgColor")),
// TODO
.__bgPos = null,
// TODO
.__neighbours = &[_]LDtk.Neighbour{},
.bgRelPath = string(level_obj.get("bgRelPath")),
.externalRelPath = string(level_obj.get("externalRelPath")),
// TODO
.fieldInstances = &[_]LDtk.FieldInstance{},
.identifier = string(level_obj.get("identifier")) orelse return error.InvalidIdentifier,
.iid = string(level_obj.get("iid")) orelse return error.InvalidIID,
// TODO
.layerInstances = null,
.pxHei = integer(level_obj.get("pxHei")) orelse return error.InvalidPxHei,
.pxWid = integer(level_obj.get("pxWid")) orelse return error.InvalidPxWid,
.uid = integer(level_obj.get("uid")) orelse return error.InvalidUID,
.worldDepth = integer(level_obj.get("worldDepth")) orelse return error.InvalidWorldDepth,
.worldX = integer(level_obj.get("worldX")) orelse return error.InvalidWorldX,
.worldY = integer(level_obj.get("worldY")) orelse return error.InvalidWorldY,
});
}
return ldtk_levels.toOwnedSlice();
}
fn object(value_opt: ?std.json.Value) ?std.json.ObjectMap {
const value = value_opt orelse return null;
return switch (value) {
.Object => |obj| obj,
else => null,
};
}
fn array(value_opt: ?std.json.Value) ?std.json.Array {
const value = value_opt orelse return null;
return switch (value) {
.Array => |arr| arr,
else => null,
};
}
fn string(value_opt: ?std.json.Value) ?[]const u8 {
const value = value_opt orelse return null;
return switch (value) {
.String => |str| str,
else => null,
};
}
fn boolean(value_opt: ?std.json.Value) ?bool {
const value = value_opt orelse return null;
return switch (value) {
.Bool => |b| b,
else => null,
};
}
fn integer(value_opt: ?std.json.Value) ?i64 {
const value = value_opt orelse return null;
return switch (value) {
.Integer => |int| int,
else => null,
};
}
fn enum_from_value(comptime T: type, value_opt: ?std.json.Value) ?T {
const value = value_opt orelse return null;
return switch (value) {
.String => |str| std.meta.stringToEnum(T, str),
else => null,
};
} }

63
src/test.ldtk Normal file
View File

@ -0,0 +1,63 @@
{
"__header__": {
"fileType": "LDtk Project JSON",
"app": "LDtk",
"doc": "https://ldtk.io/json",
"schema": "https://ldtk.io/files/JSON_SCHEMA.json",
"appAuthor": "Sebastien 'deepnight' Benard",
"appVersion": "1.1.3",
"url": "https://ldtk.io"
},
"jsonVersion": "1.1.3",
"appBuildId": 458364,
"nextUid": 1,
"identifierStyle": "Capitalize",
"worldLayout": "Free",
"worldGridWidth": 256,
"worldGridHeight": 256,
"defaultLevelWidth": 256,
"defaultLevelHeight": 256,
"defaultPivotX": 0,
"defaultPivotY": 0,
"defaultGridSize": 16,
"bgColor": "#40465B",
"defaultLevelBgColor": "#696A79",
"minifyJson": false,
"externalLevels": false,
"exportTiled": false,
"simplifiedExport": false,
"imageExportMode": "None",
"pngFilePattern": null,
"backupOnSave": false,
"backupLimit": 10,
"levelNamePattern": "Level_%idx",
"tutorialDesc": null,
"flags": [],
"defs": { "layers": [], "entities": [], "tilesets": [], "enums": [], "externalEnums": [], "levelFields": [] },
"levels": [
{
"identifier": "Level_0",
"iid": "c0773b00-02f0-11ed-bf2c-25905856c5d2",
"uid": 0,
"worldX": 0,
"worldY": 0,
"worldDepth": 0,
"pxWid": 256,
"pxHei": 256,
"__bgColor": "#696A79",
"bgColor": null,
"useAutoIdentifier": true,
"bgRelPath": null,
"bgPos": null,
"bgPivotX": 0.5,
"bgPivotY": 0.5,
"__smartColor": "#ADADB5",
"__bgPos": null,
"externalRelPath": null,
"fieldInstances": [],
"layerInstances": [],
"__neighbours": []
}
],
"worlds": []
}