From c8c86dd11dc229dd1a76c5707722ba1ac8be6bb8 Mon Sep 17 00:00:00 2001 From: Louis Pearson Date: Tue, 2 Aug 2022 23:15:51 -0600 Subject: [PATCH] Parse ldtk file without error --- src/LDtk.zig | 177 ++++++++++++++++++++++++------------------------- src/empty.ldtk | 38 ----------- src/main.zig | 143 ++++++++++++++++++++++++++++++++++++++- src/test.ldtk | 63 ++++++++++++++++++ 4 files changed, 289 insertions(+), 132 deletions(-) delete mode 100644 src/empty.ldtk create mode 100644 src/test.ldtk diff --git a/src/LDtk.zig b/src/LDtk.zig index b0ad260..d66e50c 100644 --- a/src/LDtk.zig +++ b/src/LDtk.zig @@ -1,57 +1,50 @@ const std = @import("std"); -pub fn parse(allocator: std.mem.Allocator, json_string: []const u8) !Root { - @setEvalBranchQuota(10_0000); - var tokens = std.json.TokenStream.init(json_string); - const root = try std.json.parse(Root, &tokens, .{ .allocator = allocator }); - return root; -} +// pub fn parse(parser: *std.json.Parser, json_string: []const u8) !std.json.Value { +// const value_tree = try parser.parse(json_string); +// value_tree.root.dump(); -pub fn parseFree(allocator: std.mem.Allocator, root: Root) void { - std.json.parseFree(Root, root, .{ .allocator = allocator }); -} +// const root_obj = switch (value_tree.root) { +// .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 -const Root = struct { - __header__: __Header__, - - name: []const u8, - jsonVersion: u64, - defaultPivotX: f64, - defaultPivotY: f64, - defaultGridSize: u64, +pub const Root = struct { bgColor: []const u8, - nextUid: u64, - - defs: Definitions, - + defs: ?Definitions = null, + externalLevels: bool, + jsonVersion: []const u8, levels: []Level, - - worldGridHeight: ?u64 = null, - worldGridWidth: ?u64 = null, + worldGridHeight: ?i64 = null, + worldGridWidth: ?i64 = null, worldLayout: ?WorldLayout = null, - worlds: []World, -}; - -const __Header__ = struct { - fileType: []const u8, - app: []const u8, - appAuthor: []const u8, - appVersion: []const u8, - url: []const u8, + worlds: ?[]World = null, }; /// 1.1. World -const World = struct { +pub const World = struct { identifier: []const u8, iid: []const u8, levels: []Level, - worldGridHeight: u64, - worldGridWidth: u64, + worldGridHeight: i64, + worldGridWidth: i64, worldLayout: WorldLayout, }; -const WorldLayout = enum { +pub const WorldLayout = enum { Free, GridVania, LinearHorizontal, @@ -59,59 +52,61 @@ const WorldLayout = enum { }; /// 2. Level -const Level = struct { - __bgColor: []const u8, +pub const Level = struct { + __bgColor: ?[]const u8, __bgPos: ?struct { cropRect: [4]f64, scale: [2]f64, topLeftPx: [2]i64, }, - __neighbours: []struct { - dir: []const u8, - levelIid: []const u8, - levelUid: ?u64 = null, - }, + __neighbours: []Neighbour, bgRelPath: ?[]const u8, externalRelPath: ?[]const u8, fieldInstances: []FieldInstance, identifier: []const u8, iid: []const u8, layerInstances: ?[]LayerInstance, - pxHei: u64, - pxWid: u64, - uid: u64, + pxHei: i64, + pxWid: i64, + uid: i64, worldDepth: i64, worldX: i64, worldY: i64, }; +pub const Neighbour = struct { + dir: []const u8, + levelIid: []const u8, + levelUid: ?i64 = null, +}; + /// 2.1. Layer instance const LayerInstance = struct { - __cHei: u64, - __cWid: u64, - __gridSize: u64, + __cHei: i64, + __cWid: i64, + __gridSize: i64, __identifier: []const u8, __opacity: f64, __pxTotalOffsetX: i64, __pxTotalOffsetY: i64, - __tilesetDefUid: ?u64, + __tilesetDefUid: ?i64, __tilesetRelPath: ?[]const u8, __type: []const u8, autoLayerTiles: []TileInstance, entityInstances: []EntityInstance, gridTiles: []TileInstance, iid: []const u8, - intGridCsv: []u64, - layerDefUid: u64, - levelId: u64, - overrideTilesetUid: ?u64, - pxOffsetX: u64, - pxOffsetY: u64, + intGridCsv: []i64, + layerDefUid: i64, + levelId: i64, + overrideTilesetUid: ?i64, + pxOffsetX: i64, + pxOffsetY: i64, visible: bool, /// WARNING: this deprecated value is no longer exported since version 1.0.0 /// Replaced by: intGridCsv intGrid: ?[][]const u8 = null, - // seed: u64, + // seed: i64, // autoTiles: []AutoTile, }; @@ -127,7 +122,7 @@ const TileInstance = struct { f: FlipBits, px: [2]i64, src: [2]i64, - t: u64, + t: i64, }; const FlipBits = enum(u4) { @@ -145,23 +140,23 @@ const EntityInstance = struct { __smartColor: []const u8, __tags: [][]const u8, __tile: ?TilesetRectangle, - defUid: u64, + defUid: i64, fieldInstances: []FieldInstance, - height: u64, + height: i64, iid: []const u8, px: [2]i64, - width: u64, + width: i64, }; /// 2.4. Field Instance -const FieldInstance = struct { +pub const FieldInstance = struct { __identifier: []const u8, __tile: ?TilesetRectangle, // TODO: type and value have many possible values and are not always strings. // Figure out if we can use JSON.parse for this __type: []const u8, __value: []const u8, - defUid: u64, + defUid: i64, }; const FieldType = union(enum) { @@ -209,11 +204,11 @@ const LayerDefinition = struct { Tiles, AutoLayer, }, - autoSourceLayerDefUid: ?u64, + autoSourceLayerDefUid: ?i64, displayOpacity: f64, - gridSize: u64, + gridSize: i64, 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, parallaxFactorY: f64, parallaxScaling: bool, @@ -222,12 +217,12 @@ const LayerDefinition = struct { /// 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. /// NOTE: since version 1.0.0, the old autoTilesetDefUid was removed and merged into this value. - tilesetDefUid: ?u64, + tilesetDefUid: ?i64, /// Unique Int identifier - uid: u64, + uid: i64, /// WARNING: this deprecated value will be removed completely on version 1.2.0+ /// Replaced by: tilesetDefUid - autoTilesetDefUid: ?u64 = null, + autoTilesetDefUid: ?i64 = null, }; /// 3.1.1. Auto-layer rule definition @@ -236,19 +231,19 @@ const AutoLayerRuleDefinition = opaque {}; /// 3.2. Entity definition const EntityDefinition = struct { color: []const u8, - height: u64, + height: i64, identifier: []const u8, nineSliceBorders: [4]i64, pivotX: f64, pivotY: f64, tileRect: TilesetRectangle, tileRenderMode: enum { Cover, FitInside, Repeat, Stretch, FullSizeCropped, FullSizeUncropped, NineSlice }, - tilesetId: ?u64, - uid: u64, - width: u64, + tilesetId: ?i64, + uid: i64, + width: i64, /// WARNING: this deprecated value will be removed completely on version 1.2.0+ /// Replaced by tileRect - tileId: ?u64 = null, + tileId: ?i64 = null, }; /// 3.2.1. Field definition @@ -256,52 +251,52 @@ const FieldDefinition = []const u8; /// 3.2.2. Tileset rectangle const TilesetRectangle = struct { - h: u64, - tilesetUid: u64, - w: u64, + h: i64, + tilesetUid: i64, + w: i64, x: i64, y: i64, }; /// 3.3. Tileset definition const TilesetDefinition = struct { - __cHei: u64, - __cWid: u64, + __cHei: i64, + __cWid: i64, customData: []struct { data: []const u8, - tileId: u64, + tileId: i64, }, embedAtlas: ?enum { LdtkIcons }, enumTags: []struct { enumValueId: []const u8, - tileIds: []u64, + tileIds: []i64, }, identifier: []const u8, padding: i64, - pxHei: u64, - pxWid: u64, + pxHei: i64, + pxWid: i64, relPath: ?[]const u8, spacing: i64, tags: [][]const u8, - tagsSourceEnumUid: ?u64, - tileGridSize: u64, - uid: u64, + tagsSourceEnumUid: ?i64, + tileGridSize: i64, + uid: i64, }; /// 3.4. Enum definition const EnumDefinition = struct { externalRelPath: ?[]const u8, - iconTilesetUid: ?u64, + iconTilesetUid: ?i64, identifier: []const u8, tags: [][]const u8, - uid: u64, + uid: i64, values: []EnumValueDefinition, }; /// 3.4.1. Enum value definition const EnumValueDefinition = struct { __tileSrcRect: ?[4]i64, - color: u64, + color: i64, id: []const u8, - tileId: ?u64, + tileId: ?i64, }; diff --git a/src/empty.ldtk b/src/empty.ldtk deleted file mode 100644 index 365e301..0000000 --- a/src/empty.ldtk +++ /dev/null @@ -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 - } - ] - -} diff --git a/src/main.zig b/src/main.zig index 1dacadf..ad52211 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,7 +3,144 @@ const testing = std.testing; const LDtk = @import("LDtk.zig"); test "load default/empty ldtk file" { - const empty_ldtk = @embedFile("empty.ldtk"); - const world = try LDtk.parse(testing.allocator, empty_ldtk); - _ = world; + const empty_ldtk = @embedFile("test.ldtk"); + + 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, + }; } diff --git a/src/test.ldtk b/src/test.ldtk new file mode 100644 index 0000000..6da45f1 --- /dev/null +++ b/src/test.ldtk @@ -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": [] +} \ No newline at end of file