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");
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,
};

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");
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,
};
}

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": []
}