work on trile storage
This commit is contained in:
parent
784293bf89
commit
6b62b78595
@ -1,7 +1,8 @@
|
||||
#scope_file
|
||||
|
||||
cam : Camera = .{
|
||||
far = 1000.0
|
||||
far = 2000.0,
|
||||
near = 1.0
|
||||
};
|
||||
|
||||
#scope_export
|
||||
|
||||
8744
game/resources/triles.json
Normal file
8744
game/resources/triles.json
Normal file
File diff suppressed because it is too large
Load Diff
16
modules/Jaison/.editorconfig
Normal file
16
modules/Jaison/.editorconfig
Normal file
@ -0,0 +1,16 @@
|
||||
# EditorConfig helps developers define and maintain consistent
|
||||
# coding styles between different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# We recommend you to keep these unchanged
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
4
modules/Jaison/.gitignore
vendored
Normal file
4
modules/Jaison/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
.build/
|
||||
*.dSYM
|
||||
/modules
|
||||
examples/example
|
||||
3
modules/Jaison/.gitmodules
vendored
Normal file
3
modules/Jaison/.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "unicode_utils"]
|
||||
path = unicode_utils
|
||||
url = git@github.com:rluba/jai-unicode
|
||||
21
modules/Jaison/LICENSE
Normal file
21
modules/Jaison/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Raphael Luba
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
391
modules/Jaison/examples/example.jai
Normal file
391
modules/Jaison/examples/example.jai
Normal file
@ -0,0 +1,391 @@
|
||||
#import, file "../module.jai";
|
||||
|
||||
main :: () {
|
||||
{
|
||||
// Use the temporary allocator so we don't have to worry about individually freeing
|
||||
// all the allocations.
|
||||
push_allocator(temp);
|
||||
|
||||
typed_parsing();
|
||||
typed_printing();
|
||||
generic_parsing();
|
||||
generic_printing();
|
||||
rename_by_note();
|
||||
custom_rename_procedure();
|
||||
decode_into_struct_with_using();
|
||||
|
||||
enum_errors();
|
||||
}
|
||||
|
||||
// Since the program ends here, this doesn't matter, but just setting an example.
|
||||
reset_temporary_storage();
|
||||
}
|
||||
|
||||
// Data structures for typed parsing/printing
|
||||
LevelData :: struct {
|
||||
kind: LevelKind;
|
||||
flags: LevelFlags;
|
||||
secret: bool;
|
||||
player: Entity;
|
||||
player2: *Entity;
|
||||
union {
|
||||
score: float;
|
||||
score_v2: float;
|
||||
}
|
||||
entities: [..] Entity;
|
||||
|
||||
floats: [] float;
|
||||
|
||||
LevelKind :: enum {
|
||||
EASY :: 0;
|
||||
HARD :: 1;
|
||||
LEGENDARY :: 2;
|
||||
}
|
||||
|
||||
LevelFlags :: enum_flags {
|
||||
FLAG_A :: 0x1;
|
||||
FLAG_B :: 0x2;
|
||||
FLAG_C :: 0x4;
|
||||
}
|
||||
}
|
||||
|
||||
Entity :: struct {
|
||||
name: string;
|
||||
x, y: int;
|
||||
dirty: bool; @JsonIgnore
|
||||
}
|
||||
|
||||
|
||||
LEVEL_DATA_1_JSON := #string DONE
|
||||
{"kind": ".HARD", "flags": "FLAG_A | LevelFlags.FLAG_C", "secret": false,"score":5.5,"player": {"name": "Pat","x": 10,"y": 10},"player2": {"name": "Chris"},"entities": [{"name": "fdsa","x": 0,"y": 0},{"name": "fdsa","x": 0,"y": 0}], "floats": [0.00, 1.11111111111111111, 2.0202, 3e-5, 4.444444, -5.0]}
|
||||
DONE;
|
||||
|
||||
LEVEL_DATA_2_JSON := #string DONE
|
||||
{"kind": 2, "score_v2": 25.1}
|
||||
DONE;
|
||||
|
||||
Fixed_Size_Data :: struct {
|
||||
data: [3] int;
|
||||
}
|
||||
|
||||
DATA_ARRAY_1 := #string DONE
|
||||
{"data": [1, 2, 3]}
|
||||
DONE;
|
||||
|
||||
DATA_ARRAY_2 := #string DONE
|
||||
{"data": [1, 2]}
|
||||
DONE;
|
||||
|
||||
typed_parsing :: () {
|
||||
{
|
||||
success, level := json_parse_string(LEVEL_DATA_1_JSON, LevelData, ignore_unknown=false);
|
||||
// success, level := json_parse_file("level.json", LevelData, ignore_unknown=false);
|
||||
assert(success);
|
||||
|
||||
log("Typed parsing result 1:\n%\nscore: %\n\n", level, level.score);
|
||||
assert(level.floats.count == 6);
|
||||
assert(level.floats[1] > 1); // Regression test, we had a bug here…
|
||||
}
|
||||
{
|
||||
// Test parsing integers into enum slots & alternative union fields
|
||||
success, level := json_parse_string(LEVEL_DATA_2_JSON, LevelData, ignore_unknown=false);
|
||||
assert(success);
|
||||
|
||||
log("Typed parsing result 2:\n%\nscore: %\n\n", level, level.score);
|
||||
|
||||
assert(level.kind == .LEGENDARY);
|
||||
assert(level.score == 25.1);
|
||||
}
|
||||
{
|
||||
// Test parsing into fixed-sized arrays
|
||||
success, result := json_parse_string(DATA_ARRAY_1, Fixed_Size_Data, ignore_unknown=false);
|
||||
assert(success);
|
||||
|
||||
log("Typed parsing result 3:\n%\n\n", result);
|
||||
|
||||
assert(result.data[0] == 1);
|
||||
assert(result.data[1] == 2);
|
||||
assert(result.data[2] == 3);
|
||||
}
|
||||
{
|
||||
// Test parsing into fixed-sized arrays (incorrect size)
|
||||
success, result := json_parse_string(DATA_ARRAY_2, Fixed_Size_Data, ignore_unknown=false);
|
||||
assert(!success);
|
||||
}
|
||||
}
|
||||
|
||||
typed_printing :: () {
|
||||
level := LevelData.{kind=.LEGENDARY, flags=LevelData.LevelFlags.FLAG_B|.FLAG_C, secret=true, score=500};
|
||||
level.player = .{name="Pat", x=4, y=4, dirty=true};
|
||||
array_add(*level.entities, .{name="Chris", x=6, y=6});
|
||||
|
||||
json_string := json_write_string(level);
|
||||
log("Typed printing result:\n%\n\n", json_string);
|
||||
|
||||
// success := json_write_file("level.json", level, indent_char="");
|
||||
// assert(success);
|
||||
}
|
||||
|
||||
generic_parsing :: () {
|
||||
// In this scenario, some parts of the structure are known, but other parts are not.
|
||||
|
||||
json := #string DONE
|
||||
{
|
||||
"version": 3,
|
||||
"entities": [
|
||||
{
|
||||
"name": "Player",
|
||||
"x": 2,
|
||||
"y": 2,
|
||||
"player_index": 0
|
||||
},
|
||||
{
|
||||
"name": "Snake",
|
||||
"x": 4,
|
||||
"y": 4,
|
||||
"snake_color": 1
|
||||
}
|
||||
],
|
||||
"stuff": [null, true, false]
|
||||
}
|
||||
DONE
|
||||
|
||||
success, root := json_parse_string(json);
|
||||
// success, root := json_parse_file("level.json");
|
||||
assert(success);
|
||||
|
||||
log("Generic parsing result:");
|
||||
|
||||
// Print things out, for demonstration purposes
|
||||
|
||||
traverse_node :: (node: JSON_Value, depth: int) {
|
||||
INDENTATION :: 4;
|
||||
print("% ", node.type);
|
||||
|
||||
if node.type == {
|
||||
case .NULL;
|
||||
print("\n");
|
||||
case .BOOLEAN;
|
||||
print("%\n", node.boolean);
|
||||
case .NUMBER;
|
||||
print("%\n", node.number);
|
||||
case .STRING;
|
||||
print("%\n", node.str);
|
||||
|
||||
case .OBJECT;
|
||||
print("{\n");
|
||||
for node.object {
|
||||
for 1..(depth+1)*INDENTATION print(" ");
|
||||
print("%: ", it_index);
|
||||
traverse_node(it, depth+1);
|
||||
}
|
||||
for 1..depth*INDENTATION print(" ");
|
||||
print("}\n");
|
||||
|
||||
case .ARRAY;
|
||||
print("[\n");
|
||||
for node.array {
|
||||
for 1..(depth+1)*INDENTATION print(" ");
|
||||
traverse_node(it, depth + 1);
|
||||
}
|
||||
for 1..depth*INDENTATION print(" ");
|
||||
print("]\n");
|
||||
}
|
||||
}
|
||||
|
||||
traverse_node(root, 0);
|
||||
|
||||
print("\n");
|
||||
|
||||
|
||||
|
||||
// Convenience function for grabbing object members
|
||||
get :: (json_val: JSON_Value, key: string, expected_type: JSON_Type) -> JSON_Value {
|
||||
assert(json_val.type == .OBJECT);
|
||||
table := json_val.object;
|
||||
success, val := Hash_Table.table_find_new(table, key);
|
||||
assert(success);
|
||||
assert(val.type == expected_type);
|
||||
return val;
|
||||
}
|
||||
|
||||
// Check for version number that may or may not exist
|
||||
version: float64 = -1;
|
||||
assert(root.type == .OBJECT);
|
||||
success2, val := Hash_Table.table_find_new(root.object, "version");
|
||||
if success2 {
|
||||
if val.type == .NUMBER {
|
||||
version = val.number;
|
||||
}
|
||||
}
|
||||
log("version: %\n", version);
|
||||
|
||||
// Traverse a structure we are confident about
|
||||
for get(root, "entities", .ARRAY).array {
|
||||
entity_name := get(it, "name", .STRING).str;
|
||||
x := get(it, "x", .NUMBER).number / 32;
|
||||
y := get(it, "y", .NUMBER).number / 32;
|
||||
|
||||
if entity_name == {
|
||||
case "Player";
|
||||
player_index := cast(int) get(it, "player_index", .NUMBER).number;
|
||||
log("Player with player_index=%\n", player_index);
|
||||
case "Snake";
|
||||
snake_color := cast(int) get(it, "snake_color", .NUMBER).number;
|
||||
log("Snake with snake_color=%\n", snake_color);
|
||||
case;
|
||||
//...
|
||||
}
|
||||
}
|
||||
|
||||
log("\n");
|
||||
}
|
||||
|
||||
generic_printing :: () {
|
||||
// We want to write JSON with arbitrary structure.
|
||||
|
||||
// Create and initialize object
|
||||
root_obj: JSON_Object;
|
||||
root := json_value(*root_obj);
|
||||
|
||||
// Add music index to object, in certain cases
|
||||
should_add_music := true;
|
||||
if should_add_music {
|
||||
json_set(*root_obj, "music_index", .{type=.NUMBER, number=3});
|
||||
}
|
||||
|
||||
// Create an array of values
|
||||
temp: [..] JSON_Value;
|
||||
junk := JSON_Value.{type=.STRING, str="junk"};
|
||||
array_add(*temp, junk);
|
||||
array_add(*temp, junk);
|
||||
|
||||
// Create json array value
|
||||
array := JSON_Value.{type=.ARRAY};
|
||||
array.array = temp;
|
||||
|
||||
// Add array to object
|
||||
json_set(*root_obj, "junk_array", array);
|
||||
|
||||
// Print result
|
||||
json_string := json_write_string(root);
|
||||
log("Generic_printing result:\n%\n\n", json_string);
|
||||
|
||||
//json_write_file("level.json", root);
|
||||
}
|
||||
|
||||
rename_by_note :: () {
|
||||
// Sometimes the JSON we are parsing contains members with names we cannot use or don´t wanna.
|
||||
|
||||
Message :: struct {
|
||||
value: string;
|
||||
_context: struct { // We cannot use "context" because it´s a reserved keyword in Jai.
|
||||
channel: int;
|
||||
parent: int;
|
||||
} @JsonName(context) // This member in JSON is "context" but we need to have it as "_context". So we can use the JsonName note to encode and decode it as "context".
|
||||
}
|
||||
|
||||
json := #string DONE
|
||||
{
|
||||
"value": "Hello!",
|
||||
"context": {
|
||||
"channel": 1,
|
||||
"parent": 897820
|
||||
}
|
||||
}
|
||||
DONE
|
||||
|
||||
success, message_decoded := json_parse_string(json, Message, ignore_unknown = false);
|
||||
assert(success, "Could not decode message!");
|
||||
|
||||
log("Rename_by_note decode result:\n%\n\n", message_decoded);
|
||||
assert(message_decoded._context.channel == 1);
|
||||
|
||||
|
||||
message_encoded := json_write_string(message_decoded);
|
||||
log("Rename_by_note encode result:\n%\n\n", message_encoded);
|
||||
|
||||
assert(message_encoded.count != 0);
|
||||
assert(find_index_from_left(message_encoded, "_context") == -1);
|
||||
}
|
||||
|
||||
custom_rename_procedure :: () {
|
||||
// We can also pass a custom rename procedure for renaming certain members.
|
||||
|
||||
rename_to_upper :: (member: *Type_Info_Struct_Member) -> string {
|
||||
return to_upper_copy(member.name,,temp);
|
||||
}
|
||||
|
||||
player := Entity.{
|
||||
name="Player",
|
||||
x = 10,
|
||||
y = 50
|
||||
};
|
||||
|
||||
player_encoded := json_write_string(player, rename=rename_to_upper);
|
||||
assert(player_encoded.count != 0);
|
||||
|
||||
log("encoded with rename_to_upper:\n%\n\n", player_encoded);
|
||||
assert(find_index_from_left(player_encoded, "name") == -1);
|
||||
assert(find_index_from_left(player_encoded, "NAME") != -1);
|
||||
|
||||
success, player_decoded := json_parse_string(player_encoded, Entity, ignore_unknown = false, rename = rename_to_upper);
|
||||
assert(success);
|
||||
log("decoded with rename_to_upper:\n%\n\n", player_decoded);
|
||||
assert(player_decoded.name == "Player");
|
||||
}
|
||||
|
||||
decode_into_struct_with_using :: () {
|
||||
Coordinates :: struct {
|
||||
x: int;
|
||||
y: int;
|
||||
}
|
||||
|
||||
Tile :: struct {
|
||||
color: string;
|
||||
using coordinates: Coordinates;
|
||||
}
|
||||
|
||||
JSON :: #string END
|
||||
{
|
||||
"color": "green",
|
||||
"x": 1,
|
||||
"y": 3
|
||||
}
|
||||
END
|
||||
|
||||
success, tile := json_parse_string(JSON, Tile, ignore_unknown = false);
|
||||
assert(success);
|
||||
log("Decoded tile: %", tile);
|
||||
assert(tile.color == "green");
|
||||
assert(tile.x == 1);
|
||||
assert(tile.y == 3);
|
||||
}
|
||||
|
||||
enum_errors :: () {
|
||||
// Testing error messages when parsing invalid enum values
|
||||
{
|
||||
LEVEL_DATA_BROKEN_JSON := #string DONE
|
||||
{"kind": ".VERY_HARD"}
|
||||
DONE;
|
||||
|
||||
success, level := json_parse_string(LEVEL_DATA_BROKEN_JSON, LevelData, ignore_unknown=false);
|
||||
assert(!success);
|
||||
}
|
||||
|
||||
{
|
||||
LEVEL_DATA_BROKEN_JSON := #string DONE
|
||||
{"flags": "FLAG_A | FLAG_D"}
|
||||
DONE;
|
||||
|
||||
success, level := json_parse_string(LEVEL_DATA_BROKEN_JSON, LevelData, ignore_unknown=false);
|
||||
assert(!success);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#import "Basic";
|
||||
#import "Hash_Table"; // To be able to iterate over node.object
|
||||
#import "String"; // For to_upper_copy
|
||||
#import "Compiler"; // For Type_Info_Struct_Member
|
||||
Hash_Table :: #import "Hash_Table";
|
||||
370
modules/Jaison/generic.jai
Normal file
370
modules/Jaison/generic.jai
Normal file
@ -0,0 +1,370 @@
|
||||
// Generic JSON parsing/writing functions. Result is always a JSON_Value,
|
||||
// which is awful to read and even more awful to create for complex structures.
|
||||
// But it’s useful for some cases where re-creating the whole JSON structure as
|
||||
// custom Jai struct types is inconvenient or not possible.
|
||||
|
||||
// This generic interface was the very first part I wrote in Jai and hasn’t been thorougly tested.
|
||||
// Tread with care. There may be dragons.
|
||||
|
||||
JSON_Type :: enum u8 {
|
||||
NULL :: 0;
|
||||
BOOLEAN :: 1;
|
||||
NUMBER :: 3;
|
||||
STRING :: 2;
|
||||
ARRAY :: 5;
|
||||
OBJECT :: 4;
|
||||
}
|
||||
|
||||
JSON_Value :: struct {
|
||||
type: JSON_Type;
|
||||
union {
|
||||
boolean: bool;
|
||||
number: float64;
|
||||
str: string;
|
||||
array: [] JSON_Value;
|
||||
object: *JSON_Object;
|
||||
};
|
||||
}
|
||||
|
||||
JSON_Object :: Table(string, JSON_Value);
|
||||
|
||||
json_free :: (using val: JSON_Value) {
|
||||
if #complete type == {
|
||||
case .NULL;
|
||||
case .BOOLEAN;
|
||||
case .NUMBER;
|
||||
|
||||
case .STRING;
|
||||
free(str);
|
||||
case .ARRAY;
|
||||
for array {
|
||||
json_free(it);
|
||||
}
|
||||
array_free(array);
|
||||
case .OBJECT;
|
||||
for object {
|
||||
free(it_index);
|
||||
json_free(it);
|
||||
}
|
||||
deinit(object);
|
||||
|
||||
free(object);
|
||||
}
|
||||
}
|
||||
|
||||
json_parse_string :: (content: string) -> success: bool, JSON_Value {
|
||||
if !content then return false, .{};
|
||||
|
||||
result, remainder, success := parse_value(content);
|
||||
if !success return false, result;
|
||||
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
if remainder.count {
|
||||
log_error("Unexpected trailing characters: %", remainder);
|
||||
return false, result;
|
||||
}
|
||||
|
||||
return true, result;
|
||||
}
|
||||
|
||||
// For debug purposes
|
||||
print_val :: (using val: JSON_Value) {
|
||||
if #complete type == {
|
||||
case .NULL;
|
||||
print("null");
|
||||
case .BOOLEAN;
|
||||
print("%", boolean);
|
||||
case .NUMBER;
|
||||
print("%", number);
|
||||
case .STRING;
|
||||
print("\"%\"", str);
|
||||
case .ARRAY;
|
||||
print("[");
|
||||
for array print_val(it);
|
||||
print("]");
|
||||
case .OBJECT;
|
||||
print("%", <<object);
|
||||
}
|
||||
}
|
||||
|
||||
json_value :: (str: string) -> JSON_Value {
|
||||
val: JSON_Value;
|
||||
val.type =.STRING;
|
||||
val.str = str;
|
||||
return val;
|
||||
}
|
||||
|
||||
json_value :: (obj: *JSON_Object) -> JSON_Value {
|
||||
val: JSON_Value;
|
||||
val.type =.OBJECT;
|
||||
val.object = obj;
|
||||
return val;
|
||||
}
|
||||
|
||||
json_set_null :: (val: *JSON_Value) {
|
||||
val.type = .NULL;
|
||||
}
|
||||
|
||||
json_set :: (val: *JSON_Value, value: bool) {
|
||||
val.type = .BOOLEAN;
|
||||
val.boolean = value;
|
||||
}
|
||||
|
||||
json_set :: (val: *JSON_Value, value: int) {
|
||||
val.type = .NUMBER;
|
||||
val.number = cast(float64) value;
|
||||
}
|
||||
|
||||
json_set :: (val: *JSON_Value, value: float64) {
|
||||
val.type = .NUMBER;
|
||||
val.number = value;
|
||||
}
|
||||
|
||||
json_set :: (val: *JSON_Value, value: string) {
|
||||
val.type = .STRING;
|
||||
val.str = value;
|
||||
}
|
||||
|
||||
json_set :: (val: *JSON_Value, value: [] JSON_Value) {
|
||||
val.type = .ARRAY;
|
||||
val.array = value;
|
||||
}
|
||||
|
||||
json_set :: (val: *JSON_Value, value: *JSON_Object) {
|
||||
val.type = .OBJECT;
|
||||
val.object = value;
|
||||
}
|
||||
|
||||
json_write_json_value :: (builder: *String_Builder, using val: JSON_Value, indent_char := "\t", level := 0) {
|
||||
if #complete type == {
|
||||
case JSON_Type.NULL;
|
||||
append(builder, "null");
|
||||
case JSON_Type.BOOLEAN;
|
||||
append(builder, ifx boolean "true" else "false");
|
||||
case JSON_Type.NUMBER;
|
||||
print_item_to_builder(builder, number);
|
||||
case JSON_Type.STRING;
|
||||
json_append_escaped(builder, str);
|
||||
case JSON_Type.ARRAY;
|
||||
append(builder, "[");
|
||||
for array {
|
||||
if indent_char.count {
|
||||
append(builder, "\n");
|
||||
for 0..level append(builder, indent_char);
|
||||
}
|
||||
json_write_json_value(builder, it, indent_char, level + 1);
|
||||
if it_index != array.count - 1 append(builder, ",");
|
||||
}
|
||||
if indent_char.count {
|
||||
append(builder, "\n");
|
||||
for 0..level-1 append(builder, indent_char);
|
||||
}
|
||||
append(builder, "]");
|
||||
case JSON_Type.OBJECT;
|
||||
append(builder, "{");
|
||||
obj := object;
|
||||
keys: [..] string;
|
||||
defer array_free(keys);
|
||||
array_reserve(*keys, obj.count);
|
||||
for v, k: <<obj {
|
||||
array_add(*keys, k);
|
||||
}
|
||||
intro_sort(keys, compare);
|
||||
for keys {
|
||||
if indent_char.count {
|
||||
append(builder, "\n");
|
||||
for 0..level append(builder, indent_char);
|
||||
}
|
||||
json_append_escaped(builder, it);
|
||||
append(builder, ": ");
|
||||
found, v := table_find_new(obj, it);
|
||||
assert(found, "Missing table value %", it);
|
||||
json_write_json_value(builder, v, indent_char, level + 1);
|
||||
if it_index != obj.count - 1 append(builder, ",");
|
||||
}
|
||||
if indent_char.count {
|
||||
append(builder, "\n");
|
||||
for 0..level-1 append(builder, indent_char);
|
||||
}
|
||||
append(builder, "}");
|
||||
}
|
||||
}
|
||||
|
||||
json_set :: (obj: *JSON_Object, path: string, val: JSON_Value) -> bool {
|
||||
dotpos := find_index_from_left(path, #char ".");
|
||||
if dotpos == -1 {
|
||||
table_set(obj, path, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
next := slice(path, 0, dotpos);
|
||||
remainder := advance(path, dotpos + 1);
|
||||
if !next.count return false;
|
||||
if !remainder.count return false;
|
||||
|
||||
success, next_value := table_find_new(obj, next);
|
||||
next_obj: *JSON_Object;
|
||||
if success {
|
||||
if next_value.type != JSON_Type.OBJECT return false;
|
||||
next_obj = xx next_value.object;
|
||||
} else {
|
||||
next_obj = cast(*JSON_Object) alloc(size_of(JSON_Object));
|
||||
memset(next_obj, 0, size_of(JSON_Object));
|
||||
next_value = json_value(next_obj);
|
||||
table_add(obj, next, next_value);
|
||||
}
|
||||
|
||||
return json_set(next_obj, remainder, val);
|
||||
}
|
||||
|
||||
get_as :: (val: JSON_Value, $T: Type) -> T {
|
||||
#insert #run () -> string {
|
||||
if T == bool {
|
||||
return #string END
|
||||
assert(val.type == .BOOLEAN, "Expected a % but got %", T, val.type);
|
||||
return val.boolean;
|
||||
END;
|
||||
} else if T == float || T == float64 {
|
||||
return #string END
|
||||
assert(val.type == .NUMBER, "Expected a % but got %", T, val.type);
|
||||
return cast(T) val.number;
|
||||
END;
|
||||
} else if T == string {
|
||||
return #string END
|
||||
assert(val.type == .STRING, "Expected a % but got %", T, val.type);
|
||||
return val.str;
|
||||
END;
|
||||
} else if T == [] JSON_Value {
|
||||
return #string END
|
||||
assert(val.type == .ARRAY, "Expected a % but got %", T, val.type);
|
||||
return val.array;
|
||||
END;
|
||||
} else if T == JSON_Object {
|
||||
return #string END
|
||||
assert(val.type == .OBJECT, "Expected a % but got %", T, val.type);
|
||||
return <<val.object;
|
||||
END;
|
||||
} else {
|
||||
compiler_report("Unsupported type");
|
||||
return "";
|
||||
}
|
||||
}();
|
||||
}
|
||||
|
||||
#scope_module
|
||||
|
||||
parse_value :: (to_parse: string) -> JSON_Value, remainder: string, success: bool {
|
||||
result: JSON_Value;
|
||||
remainder := trim_left(to_parse, WHITESPACE_CHARS);
|
||||
success := false;
|
||||
if remainder[0] == {
|
||||
case #char "n";
|
||||
remainder, success = expect_and_slice(remainder, "null");
|
||||
if !success return result, remainder, false;
|
||||
json_set_null(*result);
|
||||
result.type = JSON_Type.NULL;
|
||||
return result, remainder, true;
|
||||
case #char "t";
|
||||
remainder, success = expect_and_slice(remainder, "true");
|
||||
if !success return result, remainder, false;
|
||||
json_set(*result, true);
|
||||
return result, remainder, true;
|
||||
case #char "f";
|
||||
remainder, success = expect_and_slice(remainder, "false");
|
||||
if !success return result, remainder, false;
|
||||
json_set(*result, false);
|
||||
return result, remainder, true;
|
||||
case #char "\"";
|
||||
str: string;
|
||||
str, remainder, success = parse_string(remainder);
|
||||
json_set(*result, str);
|
||||
case #char "[";
|
||||
result.type = JSON_Type.ARRAY;
|
||||
result.array, remainder, success = parse_array(remainder);
|
||||
case #char "{";
|
||||
obj := cast(*JSON_Object) alloc(size_of(JSON_Object));
|
||||
<<obj, remainder, success = parse_object(remainder);
|
||||
result = json_value(obj);
|
||||
case;
|
||||
result.type = JSON_Type.NUMBER;
|
||||
result.number, success, remainder = string_to_float64(remainder);
|
||||
}
|
||||
|
||||
return result, remainder, success;
|
||||
}
|
||||
|
||||
parse_array:: (str: string) -> result: [] JSON_Value, remainder: string, success: bool {
|
||||
assert(str[0] == #char "[", "Invalid object start %", str);
|
||||
remainder := advance(str);
|
||||
result: [..] JSON_Value;
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
if remainder[0] == #char "]" {
|
||||
remainder = advance(remainder);
|
||||
return result, remainder, true;
|
||||
}
|
||||
|
||||
while true {
|
||||
value: JSON_Value;
|
||||
success: bool;
|
||||
value, remainder, success = parse_value(remainder);
|
||||
if !success return result, remainder, false;
|
||||
|
||||
array_add(*result, value);
|
||||
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
if remainder[0] != #char "," break;
|
||||
remainder = advance(remainder);
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
}
|
||||
|
||||
if remainder[0] != #char "]" return result, remainder, false;
|
||||
remainder = advance(remainder);
|
||||
return result, remainder, true;
|
||||
}
|
||||
|
||||
parse_object :: (str: string) -> result: JSON_Object, remainder: string, success: bool {
|
||||
assert(str[0] == #char "{", "Invalid object start %", str);
|
||||
remainder := advance(str);
|
||||
result: JSON_Object;
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
if remainder[0] == #char "}" {
|
||||
remainder = advance(remainder);
|
||||
return result, remainder, true;
|
||||
}
|
||||
|
||||
init(*result, 32);
|
||||
while true {
|
||||
if remainder[0] != #char "\"" return result, remainder, false;
|
||||
|
||||
key: string;
|
||||
value: JSON_Value;
|
||||
success: bool;
|
||||
key, remainder, success = parse_string(remainder);
|
||||
if !success return result, remainder, false;
|
||||
|
||||
existing := table_find_pointer(*result, key);
|
||||
if existing return result, remainder, false;
|
||||
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
if remainder[0] != #char ":" return result, remainder, false;
|
||||
remainder = advance(remainder);
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
value, remainder, success = parse_value(remainder);
|
||||
if !success return result, remainder, false;
|
||||
|
||||
table_add(*result, key, value);
|
||||
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
if remainder[0] != #char "," break;
|
||||
remainder = advance(remainder);
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
}
|
||||
|
||||
if remainder[0] != #char "}" return result, remainder, false;
|
||||
remainder = advance(remainder);
|
||||
return result, remainder, true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#import "Compiler";
|
||||
219
modules/Jaison/module.jai
Normal file
219
modules/Jaison/module.jai
Normal file
@ -0,0 +1,219 @@
|
||||
// This file contains just the JSON serialization functions. See generic.jai and typed.jai for parse fuctions.
|
||||
|
||||
// Generates a JSON string from either a JSON_Value or any custom type.
|
||||
// "indent_char" does what it says on the tin.
|
||||
// "ignore" is only used for custom types to determine which properties of your custom type should be serialized.
|
||||
// The default ignore function ignores all struct members that have the note @JsonIgnore.
|
||||
// "rename" is used for renaming certain members.
|
||||
// It gets called with the Type_Info_Struct_Member and must return the new name of the field.
|
||||
// The default procedure rename members by their @JsonName note. Eg: @JsonName(renamed_member).
|
||||
json_write_string :: (value: $T, indent_char := "\t", ignore := ignore_by_note, rename := rename_by_note) -> string {
|
||||
builder: String_Builder;
|
||||
defer free_buffers(*builder);
|
||||
json_append_value(*builder, value, indent_char, ignore, rename);
|
||||
return builder_to_string(*builder);
|
||||
}
|
||||
|
||||
json_append_value :: (builder: *String_Builder, val: $T, indent_char := "\t", ignore := ignore_by_note, rename := rename_by_note) {
|
||||
#if T == JSON_Value {
|
||||
json_write_json_value(builder, val, indent_char);
|
||||
} else {
|
||||
info := type_info(T);
|
||||
json_write_native(builder, *val, info, indent_char, ignore, rename);
|
||||
}
|
||||
}
|
||||
|
||||
// This function is useful if you have a JSON template string and just want to
|
||||
// safely insert a value without having to replicate the full json structure in Jai.
|
||||
// The return value does NOT include quotes around the string.
|
||||
//
|
||||
// Example:
|
||||
// JSON_TEMPLATE :: #string END
|
||||
// {
|
||||
// "complicated": {
|
||||
// "json": {
|
||||
// "structure": {
|
||||
// "for_a_stupid_api": {
|
||||
// "that_needs": [
|
||||
// {"a_deeply_nested_value": "%1"}
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// END
|
||||
// escaped_value := json_escape_string(my_unsafe_value);
|
||||
// defer free(escaped_value);
|
||||
// json_str := print(JSON_TEMPLATE, escaped_value);
|
||||
json_escape_string :: (str: string) -> string {
|
||||
builder: String_Builder;
|
||||
defer free_buffers(*builder);
|
||||
json_append_escaped(*builder, str);
|
||||
escaped := builder_to_string(*builder);
|
||||
return escaped;
|
||||
}
|
||||
|
||||
Ignore_Proc :: #type (member: *Type_Info_Struct_Member) -> bool;
|
||||
Rename_Proc :: #type (member: *Type_Info_Struct_Member) -> string;
|
||||
|
||||
ignore_by_note :: (member: *Type_Info_Struct_Member) -> bool {
|
||||
for note: member.notes {
|
||||
if note == "JsonIgnore" return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
rename_by_note :: (member: *Type_Info_Struct_Member) -> string {
|
||||
for note: member.notes {
|
||||
if !begins_with(note, "JsonName(") continue;
|
||||
if note.count <= 10 || note[note.count-1] != #char ")" {
|
||||
log_error("Invalid JsonName note format. Expected a name in parenthesis, but the note was \"%\".", note);
|
||||
continue;
|
||||
}
|
||||
|
||||
return slice(note, 9, note.count-10);
|
||||
}
|
||||
|
||||
return member.name;
|
||||
}
|
||||
|
||||
#scope_module
|
||||
|
||||
WHITESPACE_CHARS :: " \t\n\r";
|
||||
|
||||
#load "generic.jai";
|
||||
#load "typed.jai";
|
||||
|
||||
json_append_escaped :: (builder: *String_Builder, str: string) {
|
||||
remaining := str;
|
||||
next_pos := index_of_illegal_string_char(remaining);
|
||||
append(builder, "\"");
|
||||
while (next_pos >= 0) {
|
||||
append(builder, slice(remaining, 0, next_pos));
|
||||
if remaining[next_pos] == {
|
||||
case #char "\\";
|
||||
append(builder, "\\\\");
|
||||
case #char "\"";
|
||||
append(builder, "\\\"");
|
||||
case #char "\n";
|
||||
append(builder, "\\n");
|
||||
case #char "\r";
|
||||
append(builder, "\\r");
|
||||
case #char "\t";
|
||||
append(builder, "\\t");
|
||||
case;
|
||||
// ToDo: handle illegal multi-byte characters
|
||||
// print("Escaping: %\n\n", slice(remaining, next_pos, remaining.count - next_pos));
|
||||
print_to_builder(builder, "\\u%", formatInt(remaining[next_pos], base=16, minimum_digits=4));
|
||||
}
|
||||
remaining = advance(remaining, next_pos + 1);
|
||||
next_pos = index_of_illegal_string_char(remaining);
|
||||
}
|
||||
append(builder, remaining);
|
||||
append(builder, "\"");
|
||||
}
|
||||
|
||||
index_of_illegal_string_char :: (str: string) -> s64 {
|
||||
for 0..str.count - 1 {
|
||||
if str[it] == #char "\\" || str[it] == #char "\"" || str[it] <= 0x1F {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
expect_and_slice :: (str: string, expected: string) -> remainder: string, success: bool {
|
||||
if str.count < expected.count || !equal(slice(str, 0, expected.count), expected) {
|
||||
log_error("Unexpected token. Expected \"%\" but got: %", expected, str);
|
||||
return str, false;
|
||||
}
|
||||
remainder := advance(str, expected.count);
|
||||
return remainder, true;
|
||||
}
|
||||
|
||||
parse_string :: (str: string) -> result: string, remainder: string, success: bool {
|
||||
assert(str[0] == #char "\"", "Invalid string start %", str);
|
||||
inside := advance(str);
|
||||
needsUnescape := false;
|
||||
while inside[0] != #char "\"" {
|
||||
if inside.count < 2 return "", str, false;
|
||||
if inside[0] == #char "\\" {
|
||||
needsUnescape = true;
|
||||
if inside.count < 2 return "", str, false;
|
||||
advance(*inside);
|
||||
}
|
||||
advance(*inside);
|
||||
}
|
||||
|
||||
length := inside.data - str.data - 1;
|
||||
result := slice(str, 1, length);
|
||||
if needsUnescape {
|
||||
success: bool;
|
||||
result, success = unescape(result);
|
||||
if !success return "", str, false;
|
||||
} else {
|
||||
result = copy_string(result);
|
||||
}
|
||||
|
||||
remainder := slice(str, length + 2, str.count - length - 2);
|
||||
return result, remainder, true;
|
||||
}
|
||||
|
||||
unescape :: (str: string) -> result: string, success: bool {
|
||||
result := alloc_string(str.count);
|
||||
rc := 0;
|
||||
for i: 0..str.count-1 {
|
||||
if str[i] != #char "\\" {
|
||||
// Check for invalid characters for JSON
|
||||
if str[i] < 0x20 return "", false;
|
||||
|
||||
result[rc] = str[i];
|
||||
rc += 1;
|
||||
} else {
|
||||
if i == str.count - 1 return "", false;
|
||||
i += 1;
|
||||
if str[i] == {
|
||||
case #char "\""; #through;
|
||||
case #char "/"; #through;
|
||||
case #char "\\";
|
||||
result[rc] = str[i];
|
||||
rc += 1;
|
||||
case #char "b";
|
||||
result[rc] = 0x08;
|
||||
rc += 1;
|
||||
case #char "f";
|
||||
result[rc] = 0x0c;
|
||||
rc += 1;
|
||||
case #char "n";
|
||||
result[rc] = #char "\n";
|
||||
rc += 1;
|
||||
case #char "r";
|
||||
result[rc] = #char "\r";
|
||||
rc += 1;
|
||||
case #char "t";
|
||||
result[rc] = #char "\t";
|
||||
rc += 1;
|
||||
case #char "u";
|
||||
if i + 4 >= str.count return "", false;
|
||||
unicode_char, success := parse_unicode(slice(str, i + 1, 4));
|
||||
if !success return "", false;
|
||||
utf8_len := encode_utf8(unicode_char, *(result.data[rc]));
|
||||
rc += utf8_len;
|
||||
i += 4;
|
||||
case;
|
||||
return "", false;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.count = rc;
|
||||
return result, true;
|
||||
}
|
||||
|
||||
#import "Basic";
|
||||
#import "String";
|
||||
|
||||
#import "Hash_Table";
|
||||
#import,dir "./unicode_utils";
|
||||
#import "IntroSort";
|
||||
|
||||
63
modules/Jaison/readme.md
Normal file
63
modules/Jaison/readme.md
Normal file
@ -0,0 +1,63 @@
|
||||
# JSON serialization / deserialization module for Jai
|
||||
|
||||
*Attention: This version requires Jai beta 0.1.080!*
|
||||
Use `v1.0.0` for older betas.
|
||||
|
||||
This module offers two interfaces:
|
||||
* one uses a "generic tree" built from `JSON_Value`
|
||||
* the other is a typed version that serializes / deserializes your custom data structures.
|
||||
|
||||
The generic `JSON_Value` graphs are a pain to consume and even worse to produce by hand.
|
||||
But they allow you to parse any JSON, even if you don’t know the structure (or can’t reproduce it in Jai because it varies).
|
||||
|
||||
The typed interface is what you want for most cases.
|
||||
|
||||
## Parsing / Deserialization
|
||||
|
||||
Parsing is as simple as:
|
||||
|
||||
```Jai
|
||||
// Typed version:
|
||||
success, result := json_parse_string(json_str, Your_Type_To_Parse_Into);
|
||||
// … or if you want to get a generic structure back:
|
||||
success, result := json_parse_string(json_str);
|
||||
```
|
||||
|
||||
There are also a convenience functions for parsing if the JSON data is in a file:
|
||||
|
||||
```Jai
|
||||
success, result := json_parse_file(json_filename, Your_Type_To_Parse_Into);
|
||||
// … or
|
||||
success, result := json_parse_file(json_filename);
|
||||
```
|
||||
|
||||
|
||||
See [`typed.jai`](./typed.jai) and [`generic.jai`](./generic.jai) for details and additional options.
|
||||
|
||||
### Mixed typed and generic data
|
||||
|
||||
If you don’t know the structure of some subfield of your `Your_Type_To_Parse_Into` structure, but still want to get these values from the JSON data,
|
||||
you can declare these fields as the generic type `JSON_Value` or `*JSON_Value` and the generic parse function will take over at that point:
|
||||
|
||||
```
|
||||
Your_Type_To_Parse_Into :: struct {
|
||||
name: string;
|
||||
age: int;
|
||||
something_we_dont_know_much_about: *JSON_Value; // Whatever structure hides in the JSON, it will be parsed into JSON_Value.
|
||||
}
|
||||
```
|
||||
## Printing / Serialization
|
||||
|
||||
Generating a string works the same for both interfaces:
|
||||
|
||||
```Jai
|
||||
json_str := json_write_string(my_value);
|
||||
```
|
||||
|
||||
where `my_value` is either a `JSON_Value` or any other data structure.
|
||||
|
||||
See [`module.jai`](./module.jai) for details and additional parameters.
|
||||
|
||||
## Dependencies
|
||||
|
||||
This module uses [the `unicode_utils` module](https://github.com/rluba/jai-unicode), which is included as a submodule.
|
||||
625
modules/Jaison/typed.jai
Normal file
625
modules/Jaison/typed.jai
Normal file
@ -0,0 +1,625 @@
|
||||
|
||||
// Parse a JSON string into the given Type.
|
||||
// All members of Type that are not present in the JSON are kept at their default values.
|
||||
// All fields in the JSON that have no corresponding member in Type are ignored by default
|
||||
// but you can pass ignore_unknown = false to fail instead.
|
||||
json_parse_string :: (content: string, $T: Type, ignore_unknown := true, rename := rename_by_note) -> success: bool, T {
|
||||
result: T;
|
||||
if !content then return false, result;
|
||||
|
||||
info := type_info(T);
|
||||
remainder, success := parse_value(content, cast(*u8)*result, info, ignore_unknown, "", rename=rename);
|
||||
if !success return false, result;
|
||||
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
if remainder.count {
|
||||
log_error("Unexpected trailing characters: %", remainder);
|
||||
return false, result;
|
||||
}
|
||||
|
||||
return true, result;
|
||||
}
|
||||
|
||||
json_parse_file :: (filename: string, $T: Type, ignore_unknown := true, rename := rename_by_note) -> success: bool, T {
|
||||
file_data, success := read_entire_file(filename);
|
||||
result: T;
|
||||
if !success {
|
||||
log_error("Could not read file: %", filename);
|
||||
return false, result;
|
||||
}
|
||||
defer free(file_data);
|
||||
|
||||
if (context.log_level >= .VERBOSE) {
|
||||
log("Read file: %", success);
|
||||
}
|
||||
success, result = json_parse_string(file_data, T, ignore_unknown, rename=rename);
|
||||
return success, result;
|
||||
}
|
||||
|
||||
json_write_native :: (builder: *String_Builder, data: *void, info: *Type_Info, indent_char := "\t", ignore := ignore_by_note, rename := rename_by_note, level := 0) {
|
||||
if info.type == {
|
||||
case .BOOL;
|
||||
append(builder, ifx <<(cast(*bool) data) "true" else "false");
|
||||
case .INTEGER; #through;
|
||||
case .FLOAT;
|
||||
any_val: Any;
|
||||
any_val.type = info;
|
||||
any_val.value_pointer = data;
|
||||
print_item_to_builder(builder, any_val);
|
||||
case .ENUM;
|
||||
any_val: Any;
|
||||
any_val.type = info;
|
||||
any_val.value_pointer = data;
|
||||
|
||||
append(builder, #char "\"");
|
||||
print_item_to_builder(builder, any_val);
|
||||
append(builder, #char "\"");
|
||||
case .STRING;
|
||||
json_append_escaped(builder, <<(cast(*string) data));
|
||||
case .ARRAY;
|
||||
info_array := cast(*Type_Info_Array) info;
|
||||
element_size := info_array.element_type.runtime_size;
|
||||
assert(element_size != -1);
|
||||
|
||||
stride := element_size;
|
||||
array_data := data;
|
||||
array_count := info_array.array_count;
|
||||
if info_array.array_count == -1 {
|
||||
array_count = << cast(*s64) data;
|
||||
|
||||
array_dest: **void = data + 8;
|
||||
array_data = << array_dest;
|
||||
}
|
||||
|
||||
append(builder, "[");
|
||||
if array_data {
|
||||
if indent_char.count {
|
||||
append(builder, "\n");
|
||||
for 0..level append(builder, indent_char);
|
||||
}
|
||||
for 0..array_count-1 {
|
||||
json_write_native(builder, array_data, info_array.element_type, indent_char, ignore, rename, level + 1);
|
||||
if it != array_count - 1 append(builder, ",");
|
||||
array_data += stride;
|
||||
}
|
||||
}
|
||||
if indent_char.count {
|
||||
append(builder, "\n");
|
||||
for 0..level-1 append(builder, indent_char);
|
||||
}
|
||||
append(builder, "]");
|
||||
case .STRUCT;
|
||||
struct_info := cast(*Type_Info_Struct) info;
|
||||
if is_generic_json_value(info) {
|
||||
value := cast(*JSON_Value) data;
|
||||
json_write_json_value(builder, <<value, indent_char, level);
|
||||
} else {
|
||||
append(builder, #char "{");
|
||||
first := true;
|
||||
json_write_native_members(builder, data, struct_info.members, indent_char, ignore, rename, level, *first);
|
||||
if indent_char.count {
|
||||
append(builder, "\n");
|
||||
for 0..level-1 append(builder, indent_char);
|
||||
}
|
||||
append(builder, "}");
|
||||
}
|
||||
case .POINTER;
|
||||
ptr_info := cast(*Type_Info_Pointer) info;
|
||||
ptr := << cast(**void) data;
|
||||
if ptr {
|
||||
json_write_native(builder, ptr, ptr_info.pointer_to, indent_char, ignore, rename, level);
|
||||
} else {
|
||||
append(builder, "null");
|
||||
}
|
||||
case;
|
||||
assert(false, "Unsupported type: %", info.type);
|
||||
}
|
||||
}
|
||||
|
||||
#scope_file
|
||||
|
||||
json_write_native_members :: (builder: *String_Builder, data: *void, members: [] Type_Info_Struct_Member, indent_char := "\t", ignore := ignore_by_note, rename: Rename_Proc, level := 0, first: *bool) {
|
||||
for * member: members {
|
||||
if member.flags & .CONSTANT continue;
|
||||
if ignore(member) continue;
|
||||
if (member.type.type == .STRUCT && member.flags & .USING) {
|
||||
info := cast(*Type_Info_Struct) member.type;
|
||||
json_write_native_members(builder, data + member.offset_in_bytes, info.members, indent_char, ignore, rename, level, first);
|
||||
} else {
|
||||
if !<<first append(builder, ",");
|
||||
<<first = false;
|
||||
|
||||
if indent_char.count {
|
||||
append(builder, "\n");
|
||||
for 0..level append(builder, indent_char);
|
||||
}
|
||||
|
||||
renamed_name := rename(member);
|
||||
name := ifx renamed_name.count > 0 renamed_name else member.name;
|
||||
|
||||
json_append_escaped(builder, name);
|
||||
append(builder, ": ");
|
||||
json_write_native(builder, data + member.offset_in_bytes, member.type, indent_char, ignore, rename, level + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is_generic_json_value_or_pointer_to_it :: (info: *Type_Info) -> bool {
|
||||
value_info := info;
|
||||
if info.type == .POINTER {
|
||||
pointer_info := cast(*Type_Info_Pointer) info;
|
||||
value_info = pointer_info.pointer_to;
|
||||
}
|
||||
|
||||
return is_generic_json_value(info);
|
||||
}
|
||||
|
||||
is_generic_json_value :: (info: *Type_Info) -> bool {
|
||||
return info == type_info(JSON_Value);
|
||||
}
|
||||
|
||||
parse_value :: (to_parse: string, slot: *u8, info: *Type_Info, ignore_unknown: bool, field_name: string, rename: Rename_Proc) -> remainder: string, success: bool {
|
||||
remainder := trim_left(to_parse, WHITESPACE_CHARS);
|
||||
success := true;
|
||||
|
||||
prepare_slot :: (expected_type: Type_Info_Tag, info: *Type_Info, slot: *u8, to_parse: string) -> *u8, success: bool, is_generic: bool, info: *Type_Info {
|
||||
value_info := info;
|
||||
if info.type == .POINTER {
|
||||
pointer_info := cast(*Type_Info_Pointer) info;
|
||||
value_info = pointer_info.pointer_to;
|
||||
}
|
||||
|
||||
if info.type == .ENUM {
|
||||
info_enum := cast(*Type_Info_Enum)info;
|
||||
value_info = info_enum.internal_type;
|
||||
}
|
||||
|
||||
is_generic := is_generic_json_value(value_info);
|
||||
|
||||
if !is_generic && value_info.type != expected_type {
|
||||
teaser := to_parse;
|
||||
if teaser.count > 50 teaser.count = 50;
|
||||
builder: String_Builder;
|
||||
print_type_to_builder(*builder, info);
|
||||
type_name := builder_to_string(*builder,, temp);
|
||||
log_error("Cannot parse % value into type \"%\". Remaining input is: %…", expected_type, type_name, teaser);
|
||||
return null, false, false, value_info;
|
||||
}
|
||||
|
||||
if info.type == .POINTER {
|
||||
value_slot := alloc(value_info.runtime_size);
|
||||
initializer: (*void) #no_context;
|
||||
if value_info.type == .STRUCT {
|
||||
struct_info := cast(*Type_Info_Struct) value_info;
|
||||
initializer = struct_info.initializer;
|
||||
}
|
||||
if initializer {
|
||||
initializer(value_slot);
|
||||
} else {
|
||||
memset(value_slot, 0, value_info.runtime_size);
|
||||
}
|
||||
<<cast(**u8)slot = value_slot;
|
||||
return value_slot, true, is_generic, value_info;
|
||||
} else {
|
||||
return slot, true, is_generic, value_info;
|
||||
}
|
||||
}
|
||||
|
||||
is_generic: bool;
|
||||
if remainder[0] == {
|
||||
case #char "n";
|
||||
remainder, success = expect_and_slice(remainder, "null");
|
||||
if !success return remainder, false;
|
||||
if slot {
|
||||
if info.type == .POINTER {
|
||||
<<cast(**void) slot = null;
|
||||
} else {
|
||||
builder: String_Builder;
|
||||
print_type_to_builder(*builder, info);
|
||||
type_name := builder_to_string(*builder,, temp);
|
||||
log_error("Got NULL value for non-pointer type \"%\" of field \"%\". Keeping default value instead.", type_name, field_name);
|
||||
}
|
||||
}
|
||||
return remainder, true;
|
||||
case #char "t";
|
||||
remainder, success = expect_and_slice(remainder, "true");
|
||||
if !success return remainder, false;
|
||||
if slot {
|
||||
value_slot: *u8;
|
||||
value_slot, success, is_generic = prepare_slot(.BOOL, info, slot, to_parse);
|
||||
if success {
|
||||
if is_generic {
|
||||
json_set(cast(*JSON_Value)value_slot, true);
|
||||
} else {
|
||||
<<cast(*bool)value_slot = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
case #char "f";
|
||||
remainder, success = expect_and_slice(remainder, "false");
|
||||
if !success return remainder, false;
|
||||
if slot {
|
||||
value_slot: *u8;
|
||||
value_slot, success, is_generic = prepare_slot(.BOOL, info, slot, to_parse);
|
||||
if success {
|
||||
if is_generic {
|
||||
json_set(cast(*JSON_Value)value_slot, false);
|
||||
} else {
|
||||
<<cast(*bool)value_slot = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
case #char "\"";
|
||||
if slot && info && info.type == .ENUM {
|
||||
info_enum := cast(*Type_Info_Enum)info;
|
||||
value_slot: *u8;
|
||||
value_slot, success, is_generic = prepare_slot(.INTEGER, info_enum.internal_type, slot, to_parse);
|
||||
remainder, success = parse_enum_string(remainder, value_slot, info_enum);
|
||||
} else {
|
||||
value: string;
|
||||
value, remainder, success = parse_string(remainder);
|
||||
stored := false;
|
||||
defer if !stored free(value);
|
||||
if success && slot {
|
||||
value_slot: *u8;
|
||||
value_slot, success, is_generic = prepare_slot(.STRING, info, slot, to_parse);
|
||||
if success {
|
||||
if is_generic {
|
||||
json_set(cast(*JSON_Value)value_slot, value);
|
||||
} else {
|
||||
<<cast(*string)value_slot = value;
|
||||
}
|
||||
stored = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
case #char "[";
|
||||
value_slot: *u8;
|
||||
value_info: *Type_Info;
|
||||
if slot {
|
||||
value_slot, success, is_generic, value_info = prepare_slot(.ARRAY, info, slot, to_parse);
|
||||
}
|
||||
if success {
|
||||
if is_generic {
|
||||
value: [] JSON_Value;
|
||||
value, remainder, success = parse_array(remainder);
|
||||
json_set(cast(*JSON_Value)value_slot, value);
|
||||
} else {
|
||||
remainder, success = parse_array(remainder, value_slot, cast(*Type_Info_Array) value_info, ignore_unknown, rename=rename);
|
||||
}
|
||||
}
|
||||
case #char "{";
|
||||
value_slot: *u8;
|
||||
value_info: *Type_Info;
|
||||
if slot {
|
||||
value_slot, success, is_generic, value_info = prepare_slot(.STRUCT, info, slot, to_parse);
|
||||
}
|
||||
if success {
|
||||
if is_generic {
|
||||
value := New(JSON_Object);
|
||||
<<value, remainder, success = parse_object(remainder);
|
||||
json_set(cast(*JSON_Value)value_slot, value);
|
||||
} else {
|
||||
remainder, success = parse_object(remainder, value_slot, cast(*Type_Info_Struct) value_info, ignore_unknown, rename=rename);
|
||||
}
|
||||
}
|
||||
case;
|
||||
if slot == null || info.type == .FLOAT || is_generic_json_value_or_pointer_to_it(info) {
|
||||
float_value: float64;
|
||||
float_value, success, remainder = string_to_float64(remainder);
|
||||
if success && slot {
|
||||
value_slot: *u8;
|
||||
value_info: *Type_Info;
|
||||
value_slot, success, is_generic, value_info = prepare_slot(.FLOAT, info, slot, to_parse);
|
||||
if success {
|
||||
if is_generic {
|
||||
json_set(cast(*JSON_Value)value_slot, float_value);
|
||||
} else {
|
||||
if value_info.runtime_size == 4 {
|
||||
(<< cast(*float) slot) = cast(float) float_value;
|
||||
} else {
|
||||
assert(value_info.runtime_size == 8);
|
||||
(<< cast(*float64) slot) = float_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if slot {
|
||||
value_slot: *u8;
|
||||
value_info: *Type_Info;
|
||||
value_slot, success, is_generic, value_info = prepare_slot(.INTEGER, info, slot, to_parse);
|
||||
if success {
|
||||
if is_generic {
|
||||
int_value: s64;
|
||||
int_value, success, remainder = string_to_int(remainder, T = s64);
|
||||
if success {
|
||||
json_set(cast(*JSON_Value)value_slot, int_value);
|
||||
} else {
|
||||
log_error("Could not parse \"%\" as an integer.", to_parse);
|
||||
}
|
||||
} else {
|
||||
info_int := cast(*Type_Info_Integer) value_info;
|
||||
success, remainder = parse_and_write_integer(info_int, value_slot, to_parse);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int_value: s64;
|
||||
int_value, success, remainder = string_to_int(remainder, T = s64);
|
||||
if !success {
|
||||
log_error("Could not parse \"%\" as an integer.", to_parse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return remainder, success;
|
||||
}
|
||||
|
||||
parse_enum_string :: (str: string, slot: *u8, info_enum: *Type_Info_Enum) -> remainder: string, success: bool {
|
||||
value, remainder, success := parse_string(str);
|
||||
defer free(value);
|
||||
if !success return remainder, false;
|
||||
|
||||
// Parse by members' names
|
||||
normalize_enum_value :: inline (name: string) -> string #expand {
|
||||
normalized := trim(name);
|
||||
if normalized.count > info_enum.name.count && starts_with(normalized, info_enum.name) && normalized[info_enum.name.count] == #char "." {
|
||||
normalized = slice(normalized, info_enum.name.count+1, normalized.count-info_enum.name.count-1);
|
||||
} else if starts_with(normalized, ".") {
|
||||
normalized = slice(normalized, 1, normalized.count-1);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
int_info := info_enum.internal_type;
|
||||
int_value: s64;
|
||||
if info_enum.enum_type_flags & .FLAGS {
|
||||
values := split(value, "|",, temp);
|
||||
|
||||
for v: values {
|
||||
name := normalize_enum_value(v);
|
||||
found_name := false;
|
||||
for info_enum.names {
|
||||
if name == it {
|
||||
found_name = true;
|
||||
int_value |= info_enum.values[it_index];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !found_name {
|
||||
log_error("Enum \"%\" does not contain a member named \"%\".", info_enum.name, name);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
success = false;
|
||||
name := normalize_enum_value(value);
|
||||
for info_enum.names {
|
||||
if name == it {
|
||||
int_value = info_enum.values[it_index];
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !success {
|
||||
log_error("Enum \"%\" does not contain a member named \"%\".", info_enum.name, name);
|
||||
}
|
||||
}
|
||||
|
||||
if success {
|
||||
if int_info.signed {
|
||||
valid, low, high := Reflection.range_check_and_store(int_value, int_info, slot);
|
||||
if !valid {
|
||||
log_error("The value '%' is out of range. (It must be between % and %.)", int_value, low, high);
|
||||
return remainder, false;
|
||||
}
|
||||
} else {
|
||||
valid, low, high := Reflection.range_check_and_store(cast(u64) int_value, int_info, slot);
|
||||
if !valid {
|
||||
log_error("The value '%' is out of range. (It must be between % and %.)", int_value, low, high);
|
||||
return remainder, false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return remainder, success;
|
||||
}
|
||||
|
||||
parse_array :: (str: string, slot: *u8, info: *Type_Info_Array, ignore_unknown: bool, rename: Rename_Proc) -> remainder: string, success: bool {
|
||||
element_size: int;
|
||||
if slot {
|
||||
element_size = info.element_type.runtime_size;
|
||||
assert(element_size != -1, "Unknown element size");
|
||||
}
|
||||
|
||||
assert(str[0] == #char "[", "Invalid object start %", str);
|
||||
remainder := advance(str);
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
if remainder[0] == #char "]" {
|
||||
remainder = advance(remainder);
|
||||
// @Robustness: Do we need to zero out the array?
|
||||
return remainder, true;
|
||||
}
|
||||
|
||||
|
||||
if slot {
|
||||
array: Resizable_Array;
|
||||
|
||||
initializer: (*void) #no_context;
|
||||
if info.element_type.type == .STRUCT {
|
||||
struct_info := cast(*Type_Info_Struct) info.element_type;
|
||||
initializer = struct_info.initializer;
|
||||
}
|
||||
|
||||
while true {
|
||||
maybe_grow(*array, element_size);
|
||||
element_data := array.data + array.count * element_size;
|
||||
if initializer {
|
||||
initializer(element_data);
|
||||
} else {
|
||||
memset(element_data, 0, element_size);
|
||||
}
|
||||
|
||||
success: bool;
|
||||
remainder, success = parse_value(remainder, element_data, info.element_type, ignore_unknown, "", rename=rename);
|
||||
if !success return remainder, false;
|
||||
|
||||
array.count += 1;
|
||||
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
if remainder[0] != #char "," break;
|
||||
remainder = advance(remainder);
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
}
|
||||
|
||||
if info.array_type == .VIEW {
|
||||
view := (cast(*Array_View_64) slot);
|
||||
view.count = array.count;
|
||||
view.data = array.data;
|
||||
} else if info.array_count == -1 {
|
||||
// Resizable array
|
||||
<<(cast(*Resizable_Array) slot) = array;
|
||||
} else {
|
||||
// Fixed-size array
|
||||
if (info.array_count != array.count) {
|
||||
log_error("Expected array of size %, but found array of size %\n", info.array_count, array.count);
|
||||
return remainder, false;
|
||||
}
|
||||
|
||||
memcpy(slot, array.data, array.count * element_size);
|
||||
}
|
||||
} else {
|
||||
while true {
|
||||
success: bool;
|
||||
remainder, success = parse_value(remainder, null, null, ignore_unknown, "", rename=rename);
|
||||
if !success return remainder, false;
|
||||
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
if remainder[0] != #char "," break;
|
||||
remainder = advance(remainder);
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
}
|
||||
}
|
||||
|
||||
if remainder[0] != #char "]" return remainder, false;
|
||||
remainder = advance(remainder);
|
||||
return remainder, true;
|
||||
}
|
||||
|
||||
Member_Offset :: struct {
|
||||
member: *Type_Info_Struct_Member;
|
||||
offset_in_bytes: s64;
|
||||
}
|
||||
|
||||
// This procedure is somewhat copied from Basic.get_field.
|
||||
fill_member_table :: (table: *Table(string, Member_Offset), info: *Type_Info_Struct, rename: Rename_Proc, base_offset := 0) {
|
||||
for * member: info.members {
|
||||
offset := base_offset + member.offset_in_bytes;
|
||||
name := rename(member);
|
||||
assert(!table_find_pointer(table, name), "Redeclaration of member \"%\": % vs. %", name, <<member, <<table_find_pointer(table, name));
|
||||
table_set(table, name, .{member, offset});
|
||||
if (member.flags & .USING) && (member.type.type == .STRUCT) {
|
||||
fill_member_table(table, cast(*Type_Info_Struct)member.type, rename, offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
parse_object :: (str: string, slot: *u8, info: *Type_Info_Struct, ignore_unknown: bool, rename: Rename_Proc) -> remainder: string, success: bool {
|
||||
assert(str[0] == #char "{", "Invalid object start %", str);
|
||||
remainder := advance(str);
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
if remainder[0] == #char "}" {
|
||||
remainder = advance(remainder);
|
||||
return remainder, true;
|
||||
}
|
||||
|
||||
// @Speed: Building this table every time is pretty silly.
|
||||
// We should probably either not build it at all or cache it somewhere.
|
||||
member_table: Table(string, Member_Offset);
|
||||
init(*member_table);
|
||||
defer deinit(*member_table);
|
||||
|
||||
if info fill_member_table(*member_table, info, rename);
|
||||
|
||||
while true {
|
||||
if remainder[0] != #char "\"" return remainder, false;
|
||||
|
||||
key: string;
|
||||
success: bool;
|
||||
key, remainder, success = parse_string(remainder);
|
||||
if !success return remainder, false;
|
||||
defer free(key);
|
||||
|
||||
member_found, member_offset := table_find_new(*member_table, key);
|
||||
|
||||
member_slot: *u8;
|
||||
member_info: *Type_Info;
|
||||
if member_found {
|
||||
member_slot = slot + member_offset.offset_in_bytes;
|
||||
member_info = member_offset.member.type;
|
||||
} else if !ignore_unknown {
|
||||
log_error("Missing member % in %", key, <<info);
|
||||
return remainder, false;
|
||||
}
|
||||
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
if remainder[0] != #char ":" return remainder, false;
|
||||
remainder = advance(remainder);
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
remainder, success = parse_value(remainder, member_slot, member_info, ignore_unknown, key, rename);
|
||||
if !success return remainder, false;
|
||||
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
if remainder[0] != #char "," break;
|
||||
remainder = advance(remainder);
|
||||
remainder = trim_left(remainder, WHITESPACE_CHARS);
|
||||
}
|
||||
|
||||
if remainder[0] != #char "}" return remainder, false;
|
||||
remainder = advance(remainder);
|
||||
return remainder, true;
|
||||
}
|
||||
|
||||
parse_and_write_integer :: (info: *Type_Info_Integer, pointer: *void, string_value: string) -> bool, remainder: string {
|
||||
if info.signed {
|
||||
success, remainder := parse_and_write_integer(info, pointer, string_value, signed = true);
|
||||
return success, remainder;
|
||||
} else {
|
||||
success, remainder := parse_and_write_integer(info, pointer, string_value, signed = false);
|
||||
return success, remainder;
|
||||
}
|
||||
}
|
||||
|
||||
parse_and_write_integer :: (info: *Type_Info_Integer, pointer: *void, string_value: string, $signed: bool) -> bool, remainder: string {
|
||||
#if signed {
|
||||
Int_Type :: s64;
|
||||
} else {
|
||||
Int_Type :: u64;
|
||||
}
|
||||
|
||||
int_value, int_success, remainder := string_to_int(string_value, T = Int_Type);
|
||||
if !int_success {
|
||||
#if signed {
|
||||
log_error("Could not parse \"%\" as an integer.", string_value);
|
||||
} else {
|
||||
log_error("Could not parse \"%\" as an unsigned integer.", string_value);
|
||||
}
|
||||
return false, remainder;
|
||||
}
|
||||
|
||||
valid, low, high := Reflection.range_check_and_store(int_value, info, pointer);
|
||||
|
||||
if !valid {
|
||||
log_error("The value '%' is out of range. (It must be between % and %.)", int_value, low, high);
|
||||
return false, remainder;
|
||||
}
|
||||
|
||||
return true, remainder;
|
||||
}
|
||||
|
||||
Reflection :: #import "Reflection";
|
||||
|
||||
59
modules/Jaison/unicode_utils/module.jai
Normal file
59
modules/Jaison/unicode_utils/module.jai
Normal file
@ -0,0 +1,59 @@
|
||||
is_utf_cont :: inline (b: u8) -> bool {
|
||||
return (b & 0xc0) == 0x80;
|
||||
}
|
||||
|
||||
parse_unicode :: (str: string) -> result: u16, success: bool {
|
||||
val, success, remainder := string_to_int(str, base = 16);
|
||||
if !success || val > 0xFFFF || remainder.count return 0, false;
|
||||
return xx val, true;
|
||||
}
|
||||
|
||||
encode_utf8 :: (val: u16, result: *u8) -> len: u8 {
|
||||
if val & 0xF800 {
|
||||
result[0] = xx (0xE0 | ((val & 0xF000) >> 12));
|
||||
result[1] = xx (0x80 | ((val & 0x0FC0) >> 6));
|
||||
result[2] = xx (0x80 | (val & 0x003F));
|
||||
return 3;
|
||||
} else if val & 0x0F80 {
|
||||
result[0] = xx (0xC0 | ((val & 0x0FC0) >> 6));
|
||||
result[1] = xx (0x80 | (val & 0x003F));
|
||||
return 2;
|
||||
} else {
|
||||
result[0] = xx (val & 0x7F);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
is_valid_utf8 :: (str: string) -> valid:bool {
|
||||
for i: 0..str.count-1 {
|
||||
cur := str[i];
|
||||
if cur >= 0x80 {
|
||||
// Must be between 0xc2 and 0xf4 inclusive to be valid
|
||||
if (cur - 0xc2) > (0xf4 - 0xc2) return false;
|
||||
|
||||
if cur < 0xe0 { // 2-byte sequence
|
||||
if i + 1 >= str.count || !is_utf_cont(str[i+1]) return false;
|
||||
i += 1;
|
||||
} else if cur < 0xf0 { // 3-byte sequence
|
||||
if i + 2 >= str.count || !is_utf_cont(str[i+1]) || !is_utf_cont(str[i+2]) return false;
|
||||
|
||||
// Check for surrogate chars
|
||||
if cur == 0xed && str[i+1] > 0x9f return false;
|
||||
// ToDo: Check if total >= 0x800
|
||||
// uc = ((uc & 0xf)<<12) | ((*str & 0x3f)<<6) | (str[1] & 0x3f);
|
||||
i += 2;
|
||||
} else { // 4-byte sequence
|
||||
if i + 3 >= str.count || !is_utf_cont(str[i+1]) || !is_utf_cont(str[i+2]) || !is_utf_cont(str[i+3]) return false;
|
||||
// Make sure its in valid range (0x10000 - 0x10ffff)
|
||||
if cur == 0xf0 && str[i + 1] < 0x90 return false;
|
||||
if cur == 0xf4 && str[i + 1] > 0x8f return false;
|
||||
i += 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#scope_file
|
||||
|
||||
#import "Basic";
|
||||
@ -132,7 +132,6 @@ flush_arb_commands :: () {
|
||||
sgl_viewport(0,0,w,h,true);
|
||||
sg_apply_scissor_rect(s.x, s.y, s.w, s.h, true);
|
||||
case .REMOVE_SCISSOR;
|
||||
print("REMOVING scissor!\n");
|
||||
w,h := get_window_size();
|
||||
sg_apply_scissor_rect(0, 0, w, w, true);
|
||||
case .DRAW_TEXT;
|
||||
|
||||
@ -41,6 +41,7 @@ Level_Editor_Tab :: enum {
|
||||
};
|
||||
|
||||
current_tab : Level_Editor_Tab = .TOOLS;
|
||||
current_trile : *Trile = null;
|
||||
|
||||
get_level_editor_camera :: () -> Camera {
|
||||
camera: Camera;
|
||||
@ -196,19 +197,6 @@ draw_tacoma_tab :: (theme: *GR.Overall_Theme, total_r: GR.Rect) {
|
||||
}
|
||||
}
|
||||
|
||||
draw_trile_picker :: (theme: *GR.Overall_Theme) {
|
||||
r := GR.get_rect(ui_w(85,85), ui_h(5,0), ui_w(15, 15), ui_h(95, 0));
|
||||
draw_bg_rectangle(r, theme);
|
||||
tpt := get_trile_table_ptr();
|
||||
r.h = ui_h(4,4);
|
||||
count := 0;
|
||||
for v : tpt {
|
||||
GR.button(r, v.name, *theme.button_theme, count);
|
||||
count += 1;
|
||||
r.y += r.h;
|
||||
}
|
||||
}
|
||||
|
||||
#scope_export
|
||||
|
||||
tick_level_editor :: () {
|
||||
@ -229,7 +217,7 @@ draw_level_editor :: () {
|
||||
vs_params.mvp = mvp.floats;
|
||||
vs_params.camera = cam.position.component;
|
||||
|
||||
trilegfx := get_trile_gfx("test");
|
||||
trilegfx := get_trile_gfx(editor_current_trile.name);
|
||||
|
||||
sg_update_buffer(gPipelines.trile.bind.vertex_buffers[3], *(sg_range.{
|
||||
ptr = positions.data,
|
||||
|
||||
@ -38,39 +38,40 @@ current_tab : Trile_Editor_Tab = .TOOLSET;
|
||||
current_tool : Trile_Editor_Tool = .PAINT;
|
||||
current_mode : Trile_Editor_Tool_Mode = .AREA;
|
||||
|
||||
current_trile : *Trile;
|
||||
|
||||
#scope_export
|
||||
|
||||
new_trile :: (name: string) {
|
||||
print("Create new trile: %\n", name);
|
||||
set_trile_gfx(current_trile.name, generate_trile_gfx_matias(current_trile));
|
||||
ntrile :: (name: string) {
|
||||
set_trile_gfx(editor_current_trile.name, generate_trile_gfx_matias(editor_current_trile));
|
||||
newt : Trile;
|
||||
newt.name = sprint("%", name);
|
||||
set_trile(newt.name, newt);
|
||||
current_trile = get_trile(newt.name);
|
||||
editor_current_trile = get_trile(newt.name);
|
||||
} @Command
|
||||
|
||||
edit_trile :: (name: string) {
|
||||
set_trile_gfx(current_trile.name, generate_trile_gfx_matias(current_trile));
|
||||
current_trile = get_trile(name);
|
||||
ltrile :: (name: string) {
|
||||
set_trile_gfx(editor_current_trile.name, generate_trile_gfx_matias(editor_current_trile));
|
||||
nt := get_trile(name);
|
||||
if !nt {
|
||||
console_add_output_line("Failed to load a trile with that name...");
|
||||
return;
|
||||
}
|
||||
editor_current_trile = nt;
|
||||
} @Command
|
||||
|
||||
|
||||
#scope_file
|
||||
|
||||
apply_tool_to_trixel :: (x: s64, y: s64, z: s64) {
|
||||
if current_tool == .PAINT {
|
||||
current_trile.trixels[x][y][z].material.color = current_color;
|
||||
current_trile.trixels[x][y][z].material.metallic = metallic;
|
||||
current_trile.trixels[x][y][z].material.roughness = roughness;
|
||||
current_trile.trixels[x][y][z].material.emittance = emittance;
|
||||
editor_current_trile.trixels[x][y][z].material.color = current_color;
|
||||
editor_current_trile.trixels[x][y][z].material.metallic = metallic;
|
||||
editor_current_trile.trixels[x][y][z].material.roughness = roughness;
|
||||
editor_current_trile.trixels[x][y][z].material.emittance = emittance;
|
||||
}
|
||||
if current_tool == .ADD {
|
||||
current_trile.trixels[x][y][z].empty = false;
|
||||
editor_current_trile.trixels[x][y][z].empty = false;
|
||||
}
|
||||
if current_tool == .REMOVE {
|
||||
current_trile.trixels[x][y][z].empty = true;
|
||||
editor_current_trile.trixels[x][y][z].empty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,13 +129,13 @@ current_color : Vector3 = .{1.0, 0.0, 0.0};
|
||||
reset_trile :: () {
|
||||
newt : Trile;
|
||||
set_trile("test", newt);
|
||||
current_trile = get_trile("test");
|
||||
editor_current_trile = get_trile("test");
|
||||
} @Command
|
||||
|
||||
tick_trile_editor :: () {
|
||||
if console_open_ignore_input then return;
|
||||
|
||||
if !current_trile then current_trile = get_trile("test");
|
||||
if !editor_current_trile then editor_current_trile = get_trile("test");
|
||||
|
||||
if input_button_states[Key_Code.MOUSE_BUTTON_LEFT] & .START {
|
||||
handle_tool_click();
|
||||
@ -216,7 +217,7 @@ draw_tool_tab :: (theme: *GR.Overall_Theme, area: GR.Rect) {
|
||||
GR.slider(r, *brush_radius, 0, 12, 1, *theme.slider_theme);
|
||||
r.y += r.h * 2;
|
||||
if GR.button(r, "Save and gen", *theme.button_theme) {
|
||||
set_trile_gfx("test", generate_trile_gfx_matias(current_trile));
|
||||
set_trile_gfx(editor_current_trile.name, generate_trile_gfx_matias(editor_current_trile));
|
||||
};
|
||||
}
|
||||
|
||||
@ -258,6 +259,7 @@ draw_trile_editor :: () {
|
||||
}
|
||||
|
||||
draw_trile :: () {
|
||||
// if editor_current_trile == null then return;
|
||||
cam := get_trile_editor_camera();
|
||||
|
||||
mvp := create_viewproj(*cam);
|
||||
@ -273,6 +275,7 @@ draw_trile :: () {
|
||||
|
||||
min_distance : float = 999.0;
|
||||
|
||||
|
||||
ray := get_mouse_ray(*cam);
|
||||
|
||||
hovered_trixel_x = -1;
|
||||
@ -282,7 +285,7 @@ draw_trile :: () {
|
||||
for x: 0..15 {
|
||||
for y: 0..15 {
|
||||
for z: 0..15 {
|
||||
if current_trile.trixels[x][y][z].empty then continue;
|
||||
if editor_current_trile.trixels[x][y][z].empty then continue;
|
||||
hit := does_ray_hit_cube(ray, .{ .{x * TRIXEL_SIZE, y * TRIXEL_SIZE, z * TRIXEL_SIZE}, .{TRIXEL_SIZE, TRIXEL_SIZE, TRIXEL_SIZE}});
|
||||
if hit.hit && hit.distance < min_distance {
|
||||
hovered_trixel_x = x;
|
||||
@ -299,13 +302,13 @@ draw_trile :: () {
|
||||
for x: 0..15 {
|
||||
for y: 0..15 {
|
||||
for z: 0..15 {
|
||||
if current_trile.trixels[x][y][z].empty then continue;
|
||||
if editor_current_trile.trixels[x][y][z].empty then continue;
|
||||
|
||||
trixels[trixel_count].pos.x = x * (1.0 / 16.0) + TRIXEL_SIZE_HALF;
|
||||
trixels[trixel_count].pos.y = y * (1.0 / 16.0) + TRIXEL_SIZE_HALF;
|
||||
trixels[trixel_count].pos.z = z * (1.0 / 16.0) + TRIXEL_SIZE_HALF;
|
||||
trixels[trixel_count].pos.w = 1.0;
|
||||
trixel_color := current_trile.trixels[x][y][z].material.color;
|
||||
trixel_color := editor_current_trile.trixels[x][y][z].material.color;
|
||||
|
||||
if hovered_trixel_x == x &&
|
||||
hovered_trixel_y == y &&
|
||||
@ -313,7 +316,7 @@ draw_trile :: () {
|
||||
trixel_color = .{1.0, 0.0, 0.0};
|
||||
}
|
||||
|
||||
trixels[trixel_count].col = .{trixel_color.x, trixel_color.y, trixel_color.z, material_encode_to_float(current_trile.trixels[x][y][z].material)};
|
||||
trixels[trixel_count].col = .{trixel_color.x, trixel_color.y, trixel_color.z, material_encode_to_float(editor_current_trile.trixels[x][y][z].material)};
|
||||
trixel_count += 1;
|
||||
}
|
||||
}
|
||||
@ -394,4 +397,6 @@ draw_trile_editor_ui :: (theme: *GR.Overall_Theme) {
|
||||
case .TOOLSET;
|
||||
draw_tool_tab(theme, r);
|
||||
}
|
||||
|
||||
draw_trile_picker(theme);
|
||||
}
|
||||
|
||||
13
src/load.jai
13
src/load.jai
@ -82,6 +82,19 @@ create_texture_from_pack :: (path: string) -> sg_image {
|
||||
return img;
|
||||
}
|
||||
|
||||
load_string_from_pack :: (path: string) -> string {
|
||||
ok, entry := table_find_new(*g_asset_pack.lookup, path);
|
||||
if !ok {
|
||||
print("Failed to load string from pack: %\n", path);
|
||||
return "";
|
||||
}
|
||||
|
||||
s: string;
|
||||
s.data = entry.data.data;
|
||||
s.count = entry.data.count;
|
||||
return s;
|
||||
}
|
||||
|
||||
asset_list :: () -> string #expand {
|
||||
count := 0;
|
||||
for v : g_asset_pack.lookup {
|
||||
|
||||
10
src/main.jai
10
src/main.jai
@ -101,7 +101,17 @@ init_after_asset_pack :: () {
|
||||
ui_init_font_fields(*state.font_default);
|
||||
|
||||
init_ui();
|
||||
ltriles();
|
||||
tt := get_trile_table_ptr();
|
||||
if tt.count == 0 {
|
||||
set_trile("test", Trile.{});
|
||||
} else {
|
||||
name : string;
|
||||
for v : tt {
|
||||
name = v.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
init_editor();
|
||||
game_init();
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ trile_table : Table(string, Trile);
|
||||
|
||||
#scope_export
|
||||
|
||||
|
||||
editor_current_trile : *Trile = null;
|
||||
|
||||
Trile_GFX :: struct {
|
||||
trixel_colors : sg_image;
|
||||
@ -58,6 +58,7 @@ set_trile_gfx :: (name: string, gfx: Trile_GFX, skip_preexist_check: bool = fals
|
||||
}
|
||||
|
||||
set_trile :: (name: string, trile: Trile) {
|
||||
print("Setting trile with name: %\n",name);
|
||||
table_set(*trile_table, name, trile);
|
||||
}
|
||||
|
||||
@ -70,7 +71,7 @@ get_trile :: (name: string) -> (*Trile, success: bool) {
|
||||
return trileptr, true;
|
||||
}
|
||||
|
||||
list_trile :: () -> string {
|
||||
lstrile :: () -> string {
|
||||
count := 0;
|
||||
for v : trile_table {
|
||||
console_add_output_line(sprint("%", v.name));
|
||||
@ -79,6 +80,31 @@ list_trile :: () -> string {
|
||||
return tprint("% triles", count);
|
||||
} @Command
|
||||
|
||||
striles :: () {
|
||||
Jaison :: #import "Jaison";
|
||||
triles : [..]TrileSerialize;
|
||||
triles.allocator = temp;
|
||||
for v : trile_table {
|
||||
array_add(*triles, trile_to_serialize_form(v));
|
||||
}
|
||||
#if OS != .WASM {
|
||||
file :: #import "File";
|
||||
json := Jaison.json_write_string(triles, " ");
|
||||
file.write_entire_file("./game/resources/triles.json", json);
|
||||
|
||||
}
|
||||
} @Command
|
||||
|
||||
ltriles :: () {
|
||||
Jaison :: #import "Jaison";
|
||||
s := load_string_from_pack("./game/resources/triles.json");
|
||||
success, triles := Jaison.json_parse_string(s, [..]TrileSerialize,, temp);
|
||||
for triles {
|
||||
set_trile(sprint("%",it.name), trile_from_serialize_form(it));
|
||||
print("Loaded %\n", it.name);
|
||||
}
|
||||
} @Command
|
||||
|
||||
Material :: struct {
|
||||
addRoughness : u8 = 0;
|
||||
roughness : u8 = 4;
|
||||
@ -97,6 +123,45 @@ Trile :: struct {
|
||||
trixels : [16][16][16] Trixel;
|
||||
};
|
||||
|
||||
TrixelSerialize :: [5]u8;
|
||||
|
||||
TrileSerialize :: struct {
|
||||
name : string = "test";
|
||||
trixels : [16][16][16] TrixelSerialize;
|
||||
};
|
||||
|
||||
trile_to_serialize_form :: (t: Trile) -> TrileSerialize {
|
||||
ts := TrileSerialize.{
|
||||
name = t.name,
|
||||
};
|
||||
for i: 0..15 {
|
||||
for j: 0..15 {
|
||||
for k: 0..15 {
|
||||
r,g,b,a := material_to_rgba(t.trixels[i][j][k].material);
|
||||
ts.trixels[i][j][k] = .[ifx t.trixels[i][j][k].empty then cast(u8)0 else cast(u8)1, r,g,b,a];
|
||||
}
|
||||
}
|
||||
}
|
||||
return ts;
|
||||
}
|
||||
|
||||
trile_from_serialize_form :: (ts: TrileSerialize) -> Trile {
|
||||
t := Trile.{
|
||||
name = sprint("%",ts.name)
|
||||
};
|
||||
for i: 0..15 {
|
||||
for j: 0..15 {
|
||||
for k: 0..15 {
|
||||
matinfo := ts.trixels[i][j][k];
|
||||
mat := material_from_rgba(matinfo[1], matinfo[2], matinfo[3], matinfo[4]);
|
||||
t.trixels[i][j][k].material = mat;
|
||||
t.trixels[i][j][k].empty = cast(bool) matinfo[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
material_encode_to_char :: (mat: Material) -> u8 {
|
||||
return (mat.addRoughness & 0x1) | ((mat.emittance & 0x3) << 1) |
|
||||
((mat.metallic & 0x3) << 3) | ((mat.roughness & 0x7) << 5);
|
||||
@ -106,6 +171,14 @@ material_encode_to_float :: (mat: Material) -> float {
|
||||
return cast(float)(material_encode_to_char(mat)) / 255.0;
|
||||
}
|
||||
|
||||
material_decode_from_char :: (packedMaterial: u8) -> Material {
|
||||
mat : Material;
|
||||
mat.emittance = (packedMaterial >> 1) & 3;
|
||||
mat.roughness = (packedMaterial >> 5) & 7;
|
||||
mat.metallic = (packedMaterial >> 3) & 3;
|
||||
return mat;
|
||||
}
|
||||
|
||||
material_to_rgba :: (mat: Material) -> (r: u8, g: u8, b: u8, a: u8) {
|
||||
r : u8 = cast(u8) (mat.color.x * 255.0);
|
||||
g : u8 = cast(u8) (mat.color.y * 255.0);
|
||||
@ -114,4 +187,26 @@ material_to_rgba :: (mat: Material) -> (r: u8, g: u8, b: u8, a: u8) {
|
||||
return r,g,b,a;
|
||||
}
|
||||
|
||||
material_from_rgba :: (r: u8, g: u8, b: u8, a: u8) -> Material {
|
||||
mat := material_decode_from_char(a);
|
||||
mat.color.x = (cast(float) r)/255.0;
|
||||
mat.color.y = (cast(float) g)/255.0;
|
||||
mat.color.z = (cast(float) b)/255.0;
|
||||
return mat;
|
||||
}
|
||||
|
||||
draw_trile_picker :: (theme: *GR.Overall_Theme) {
|
||||
r := GR.get_rect(ui_w(85,85), ui_h(5,0), ui_w(15, 15), ui_h(95, 0));
|
||||
draw_bg_rectangle(r, theme);
|
||||
tpt := get_trile_table_ptr();
|
||||
r.h = ui_h(4,4);
|
||||
count := 0;
|
||||
for v : tpt {
|
||||
print("Current trile name: %\n", editor_current_trile.name);
|
||||
if GR.button(r, v.name, *t_button_selectable(theme, editor_current_trile != null && editor_current_trile.name == v.name), count) {
|
||||
editor_current_trile = get_trile(v.name);
|
||||
}
|
||||
count += 1;
|
||||
r.y += r.h;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user