initial commit

This commit is contained in:
Rowan 2025-06-06 01:49:16 -04:00
commit 739752e75b
77 changed files with 11310 additions and 0 deletions

2
.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

4
godot/.editorconfig Normal file
View file

@ -0,0 +1,4 @@
root = true
[*]
charset = utf-8

View file

@ -0,0 +1,28 @@
class_name Convert
class FromString:
static func _invalid_type(value: String, type: String) -> Error:
return Error.new('"%s" is not a valid %s')
static func to_int(value: String) -> Result:
if value.is_valid_int():
return Result.ok(value.to_int())
else: return Result.err(_invalid_type(value, 'int'))
static func to_float(value: String) -> Result:
if value.is_valid_float():
return Result.ok(value.to_float())
else: return Result.err(_invalid_type(value, 'float'))
static func to_bool(value: String) -> Result:
if value == 'true': return Result.ok(true)
elif value == 'false': return Result.ok(false)
else: return Result.err(_invalid_type(value, 'bool'))
static func binary_to_int(value: String) -> int:
return value.bin_to_int()
static func hex_to_int(value: String) -> Result:
if value.is_valid_hex_number():
return Result.ok(value.hex_to_int())
else: return Result.err(_invalid_type(value, 'hex string'))

View file

@ -0,0 +1 @@
uid://3fd4av3spg8k

View file

@ -0,0 +1,33 @@
class_name Error extends RefCounted
var message: String
var stack: Array
func _init(message: String):
self.message = message
stack = get_stack().slice(1)
func raise_self():
Error.raise(message)
static func raise(message: String):
push_error(message)
assert(false)
func _to_string() -> String:
var str = message
for value in stack:
str += "\r\n%s:%s - at function %s" % [value.source, value.line, value.function]
return str
class NotImplemented extends Error:
static var _message: String = "NotImplementedError: %s is not implemented"
func _init(name: String):
super(_message % name)
class InvalidArgument extends Error:
static var _message: String = 'InvalidArgumentError: %s is not a valid argument'
func _init(arg: Variant):
super(_message % arg)

View file

@ -0,0 +1 @@
uid://ds0dnuvhjij4h

View file

@ -0,0 +1,24 @@
class_name IndexedIterator extends Iterator
class Indexed extends RefCounted:
var value: Variant
var index: int
func _init(_value: Variant, _index: int) -> void:
value = _value
index = _index
var _iter: Iterator
var _index: int = 0
func _init(iter: Iterator) -> void:
_iter = iter
func _add_index(value: Variant) -> Indexed:
return Indexed.new(value, _index)
func clone() -> IndexedIterator:
return IndexedIterator.new(_iter.clone())
func next() -> Option:
return _iter.next().map(_add_index)

View file

@ -0,0 +1 @@
uid://b2w3v1h5ex4tm

View file

@ -0,0 +1,23 @@
class_name Iterator extends RefCounted
func clone() -> Iterator:
return Iterator.new()
func next() -> Option:
return Option.none
func into_peekable() -> PeekableIter:
return PeekableIter.new(self)
func into_indexed() -> IndexedIterator:
return IndexedIterator.new(self)
func _iter_init(iter: Array) -> bool:
return _iter_next(iter)
func _iter_next(iter: Array) -> bool:
iter[0] = next()
return iter[0].is_some()
func _iter_get(iter: Variant) -> Variant:
return iter.unwrap()

View file

@ -0,0 +1 @@
uid://r3doxerf7d0j

View file

@ -0,0 +1,17 @@
class_name ListIterator extends Iterator
var _value: Variant
var _iter: RangeIterator
func _init(value: Variant, iter: RangeIterator) -> void:
_value = value
_iter = iter
func _at(index: int) -> Variant:
return _value[index]
func clone() -> ListIterator:
return ListIterator.new(_value, _iter.clone())
func next() -> Option:
return _iter.next().map(_at)

View file

@ -0,0 +1 @@
uid://hpnk6jd2353w

View file

@ -0,0 +1,23 @@
class_name PeekableIter extends Iterator
var _iter: Iterator
var _peeked: Variant
func _init(iter: Iterator) -> void:
_iter = iter
func clone() -> PeekableIter:
return PeekableIter.new(_iter.clone())
func peek() -> Option:
if _peeked == null:
_peeked = _iter.next()
return _peeked
func next() -> Option:
if _peeked == null:
return _iter.next()
else:
var value = _peeked
_peeked = null
return value

View file

@ -0,0 +1 @@
uid://u6wma683vqer

View file

@ -0,0 +1,19 @@
class_name RangeIterator extends Iterator
var _index: int
var _to: int
var _step: int
func _init(from: int, to: int, step: int = 1) -> void:
_index = from - 1
_to = to
_step = step
func clone() -> RangeIterator:
return RangeIterator.new(_index, _to, _step)
func next() -> Option:
if _index < _to:
_index += 1
return Option.some(_index)
else: return Option.none

View file

@ -0,0 +1 @@
uid://dqqwqcdx75cfu

View file

@ -0,0 +1,16 @@
class_name TakeWhileIter extends Iterator
var _iter: Iterator
var _predicate: Callable
func _init(iter: Iterator, predicate: Callable) -> void:
_iter = iter
_predicate = predicate
func clone() -> TakeWhileIter:
return self
func next() -> Option:
match _iter.next().filter(_predicate):
var x when x.is_some(): return x
_: return Option.none

View file

@ -0,0 +1 @@
uid://cdj83mvbbfvjj

View file

@ -0,0 +1,18 @@
class_name VariantIterator extends Iterator
var _iterable: Variant
var _state: Array
func _init(value) -> void:
_iterable = value
_state = value._iter_init([])
func clone() -> VariantIterator:
var new = VariantIterator.new(_iterable)
new._state = _state
return new
func next() -> Option:
if _iterable._iter_next(_state):
return Option.some(_iterable._iter_get(_state[0]))
else: return Option.none

View file

@ -0,0 +1 @@
uid://g86f3fcv5pra

View file

@ -0,0 +1,87 @@
class_name Option extends RefCounted
static var Unit = Option.some(null)
static func some(value: Variant) -> Option:
return Some.new(value)
static var none: Option.None = None.new()
static func collect_some(options: Array[Option]) -> Option:
match options:
[]: return Option.none
[var x]: return x
_:
var result = []
for option in options:
if option.is_some():
result.push_back(option.unwrap())
else:
return Option.none
return Option.some(result)
class Some extends Option:
var value: Variant
func _init(val: Variant) -> void:
value = val
func chain(fn: Callable) -> Option:
return fn.call(value)
func filter(fn: Callable) -> Option:
if fn.call(value): return self
else: return Option.none
func flatten() -> Option:
if value is Option:
return value.flatten()
else: return Option.some(value)
func map(fn: Callable) -> Option:
return Option.some(fn.call(value))
func is_some() -> bool:
return true
func is_none() -> bool:
return false
func or_default(_default_value: Variant) -> Variant:
return value
func or_else(_fn: Callable) -> Variant:
return value
func unwrap() -> Variant:
return value
class None extends Option:
func chain(_fn: Callable) -> Option:
return self
func filter(_f: Callable) -> Option:
return self
func flatten() -> Option:
return self
func map(_fn: Callable) -> Option:
return self
func is_some() -> bool:
return false
func is_none() -> bool:
return true
func or_default(default_value: Variant) -> Variant:
return default_value
func or_else(fn: Callable) -> Variant:
return fn.call()
func unwrap() -> Variant:
return null

View file

@ -0,0 +1 @@
uid://k2qcgaguq3o0

View file

@ -0,0 +1,99 @@
class_name Result extends RefCounted
static var Unit: Result = Result.ok(null)
static func ok(value: Variant) -> Result:
return Ok.new(value)
static func err(error: Variant) -> Result:
return Err.new(error)
static func collect_ok(results: Array[Result]) -> Result:
match results:
[]: return Result.Unit
[var x]: return x
_:
var res = []
for result in results:
if result.is_ok():
res.push_back(result.unwrap())
else:
return result
return Result.ok(res)
class Ok extends Result:
var value: Variant
func _init(value):
self.value = value
func to_option() -> Option:
return Option.some(value)
func chain(fn: Callable) -> Result:
return fn.call(value)
func filter(fn: Callable, fail) -> Result:
if fn.call(value):
return self
else:
return Result.err(fail)
func map(fn: Callable) -> Result:
return Result.ok(fn.call(value))
func is_ok() -> bool:
return true
func is_err() -> bool:
return false
func or_default(_default_value: Variant) -> Variant:
return value
func or_else(_fn: Callable) -> Variant:
return value
func unwrap() -> Variant:
return value
func unwrap_err() -> Variant:
return null
class Err extends Result:
var error: Variant
func _init(error) -> void:
self.error = error
func to_option() -> Option:
return Option.none
func chain(_fn: Callable) -> Result:
return self
func filter(_fn: Callable, fail) -> Result:
return Result.err(fail)
func map(fn: Callable) -> Result:
return self
func is_ok() -> bool:
return false
func is_err() -> bool:
return true
func or_default(default_value: Variant) -> Variant:
return default_value
func or_else(fn: Callable) -> Variant:
return fn.call()
func unwrap() -> Variant:
return null
func unwrap_err() -> Variant:
return error

View file

@ -0,0 +1 @@
uid://do2wtq5541j5d

View file

View file

@ -0,0 +1 @@
uid://cnbk0hdc5ngxo

View file

@ -0,0 +1,62 @@
class_name Encoding extends RefCounted
enum Type {
Ascii,
Utf8,
Utf16,
Utf32,
Wchar
}
static var Ascii = from(Type.Ascii)
static var Utf8 = from(Type.Utf8)
static var Utf16 = from(Type.Utf16)
static var Utf32 = from(Type.Utf32)
static var Wchar = from(Type.Wchar)
var type: Type
func _init(_type: Type) -> void:
type = _type
static func from(type: Type) -> Encoding:
return Encoding.new(type)
static func byte_size_of(encoding: Type) -> int:
match encoding:
Type.Ascii: return 1
Type.Utf8: return 1
Type.Utf16: return 2
Type.Utf32: return 4
Type.Wchar:
match OS.get_name():
"Windows": return 2
_: return 4
return -1
func byte_size() -> int:
return byte_size_of(type)
static func encode_str_as(value: String, encoding: Type) -> PackedByteArray:
match encoding:
Type.Ascii: return value.to_ascii_buffer()
Type.Utf8: return value.to_utf8_buffer()
Type.Utf16: return value.to_utf16_buffer()
Type.Utf32: return value.to_utf32_buffer()
Type.Wchar: return value.to_wchar_buffer()
return PackedByteArray()
func encode_str(value: String) -> PackedByteArray:
return encode_str_as(value, type)
static func decode_str_as(value: PackedByteArray, encoding: Type) -> String:
match encoding:
Type.Ascii: return value.get_string_from_ascii()
Type.Utf8: return value.get_string_from_utf8()
Type.Utf16: return value.get_string_from_utf16()
Type.Utf32: return value.get_string_from_utf32()
Type.Wchar: return value.get_string_from_wchar()
return ""
func decode_str(value: PackedByteArray) -> String:
return decode_str_as(value, type)

View file

@ -0,0 +1 @@
uid://mkxfk1awv1fv

364
godot/addons/serde/json.gd Normal file
View file

@ -0,0 +1,364 @@
class_name Json
static func stringify(value: Variant, allow_eval: bool = false) -> String:
var ser = Json.Serializer.new(allow_eval)
Serde.serialize(ser, value, allow_eval)
return ser.output
static func parse_string(value: String, into: Variant = null, allow_eval: bool = false) -> Variant:
var de = Json.Deserializer.from_string(value)
if into != null and into.has_method(Serde.DeserializeMethod):
return into.call(Serde.DeserializeMethod, de)
else:
return de.deserialize_any(Serde.GenericVisitor.new())
class SerializeSeq extends Serde.SerializeSeq:
var ser: Json.Serializer
var first: bool = true
func _init(ser: Json.Serializer) -> void:
self.ser = ser
ser.write('[')
func serialize_element(value: Variant) -> Result:
if not first: ser.write(',')
first = false
Serde.serialize(ser, value, ser.allow_eval)
return Result.Unit
func end() -> Result:
ser.write(']')
return Result.Unit
class SerializeMap extends Serde.SerializeMap:
var ser: Json.Serializer
var first: bool = true
func _init(ser: Json.Serializer) -> void:
self.ser = ser
ser.write('{')
func serialize_key(value: Variant) -> Result:
if not first: ser.write(',')
first = false
Serde.serialize(ser, value, ser.allow_eval)
return Result.Unit
func serialize_value(value: Variant) -> Result:
ser.write(':')
Serde.serialize(ser, value, ser.allow_eval)
return Result.Unit
func end() -> Result:
ser.write('}')
return Result.Unit
class Serializer extends Serde.Serializer:
var output: String
var allow_eval: bool
func _init(allow_eval: bool = false) -> void:
self.allow_eval = allow_eval
func write(value: String) -> Result:
output += value
return Result.Unit
func write_string(value: Variant) -> Result:
return write(str(value))
func serialize_nil() -> Result:
return write("null")
func serialize_bool(value: bool) -> Result:
return write_string(value)
func serialize_int(value: int):
return write_string(value)
func serialize_float(value: float) -> Result:
return write_string(value)
func serialize_string(value: String):
return write('"%s"' % value)
func serialize_seq(_len: int):
return Json.SerializeSeq.new(self)
func serialize_dict(_len: int):
return Json.SerializeMap.new(self)
func serialize_object(_name: StringName, len: int):
return serialize_dict(len)
class Parser extends RefCounted:
var de: Json.Deserializer
var iter: PeekableIter
func _init(de: Json.Deserializer) -> void:
self.de = de
iter = de._iter
static func eq(a: Variant, b: Variant) -> bool:
return a == b
static func _join(value: Array) -> String:
return "".join(value)
static func _collect(results: Array[Result]) -> Result:
return Result.collect_ok(results).map(_join)
func any() -> Result:
match iter.peek():
var x when x.is_some():
return Result.ok(iter.next().unwrap())
_: return Result.err(Error.new("eof"))
func char(ch: String) -> Result:
match iter.peek():
var x when x.filter(eq.bind(ch)).is_some(): return Result.ok(iter.next().unwrap())
var x: return Result.err(Error.new('expecting "%s", got "%s"' % [ch, x.unwrap()]))
func str(value: String) -> Result:
for i in range(0, value.length()):
var ch = self.char(value[i])
if ch.is_err(): return ch
return Result.ok(value)
func any_of(parsers: Array[Callable]) -> Result:
for parser in parsers:
var result = parser.call()
if result.is_ok(): return result
return Result.err(Error.new('none of the provided parsers matched input'))
func all_of(parsers: Array) -> Result:
var results: Array[String] = []
for parser in parsers:
match parser.call():
var x when x.is_ok(): results.append(x.unwrap())
var x: return x
return Result.ok(results)
func many(parser: Callable, limit: int = 1000) -> Result:
var results: Array[String] = []
var value = parser.call()
while value.is_ok() and limit > 0:
results.append(value.unwrap())
value = parser.call()
limit -= 1
if limit <= 0: return Result.err(Error.new("executed too many times (%s)" % limit))
elif len(results) == 0: return Result.err(Error.new("many matched nothing"))
else: return Result.ok(results)
func until(parser: Callable) -> Result:
return many(
func ():
match parser.call():
var x when x.is_err(): return any()
_: return Result.err(null)
)
func negate(parser: Callable) -> Result:
match parser.call():
var x when x.is_ok(): return Result.err('negated parser passed with value "%s"' % x.unwrap())
_: return Result.Unit
func skip(parser: Callable) -> Result:
return parser.call().chain(func(_v: Variant) -> Result: return Result.ok(''))
func any_char(chars: String) -> Result:
for ch in chars:
if self.char(ch).is_ok(): return Result.ok(ch)
return Result.err(Error.new('expected any of "%s"' % chars))
func optional(parser: Callable, default_value: Variant = '') -> Result.Ok:
match parser.call():
var x when x.is_ok(): return x
_: return Result.ok(default_value)
func ws() -> Result:
return any_char(' \t')
func skip_ws() -> Result:
return skip(many.bind(ws))
func parse_sign() -> Result:
return any_char('+-')
func parse_digit() -> Result:
return any_char('1234567890')
func parse_digits() -> Result:
return many(parse_digit).map("".join)
func parse_integer() -> Result:
if self.char('0').is_ok(): return Result.ok(0)
var sign = optional(parse_sign)
var digits = parse_digits()
return _collect([sign, digits])
func parse_exponent() -> Result:
var e = any_char('eE')
var sign = optional(parse_sign)
var digits = parse_digits()
return _collect([e, sign, digits])
func parse_fractional() -> Result:
var point = self.char('.')
var digits = parse_digits()
var exp = optional(parse_exponent)
return _collect([point, digits, exp])
func parse_float() -> Result:
var integer = parse_integer()
var fractional = optional(parse_fractional)
return _collect([integer, fractional])
func parse_string() -> Result:
var quote = char.bind('"')
var slash = self.char.bind('\\')
var unescaped_quote = all_of.bind([negate.bind(slash), quote])
var open = skip(quote)
var str_value = until(unescaped_quote).map(_join)
return _collect([open, str_value])
class SeqAccess extends Serde.SeqAccess:
var de: Json.Deserializer
var first: bool = true
var parser: Parser:
get: return de._parser
func _init(de: Json.Deserializer) -> void:
self.de = de
func next_element() -> Result:
parser.skip_ws()
if parser.char(']').is_ok():
return Result.ok(Option.Unit)
if not first and parser.char(',').is_err():
return Result.err(Error.new('expected comma'))
first = false
parser.skip_ws()
return de.deserialize_any(Serde.GenericVisitor.new()).map(Option.some)
class MapAccess extends Serde.MapAccess:
var de: Json.Deserializer
var first: bool = true
var iter: PeekableIter:
get: return de._iter
var parser: Parser:
get: return de._parser
func _init(de: Json.Deserializer) -> void:
self.de = de
func next_key() -> Result:
parser.skip_ws()
if iter.peek().filter(Parser.eq.bind('}')).is_some():
return Result.ok(Option.Unit)
if not first:
var comma = parser.char(',')
if comma.is_err(): return comma
first = false
parser.skip_ws()
var key = de.deserialize_any(Serde.GenericVisitor.new()).map(Option.some)
return key
func next_value() -> Result:
parser.skip_ws()
var colon = parser.char(':')
if colon.is_err(): return colon
parser.skip_ws()
return de.deserialize_any(Serde.GenericVisitor.new()).map(Option.some)
class Deserializer extends Serde.Deserializer:
var _input: String
var _iter: PeekableIter
var _parser: Parser
func _init(value: String) -> void:
_input = value
_iter = ListIterator.new(_input, RangeIterator.new(0, len(_input))).into_peekable()
_parser = Parser.new(self)
static func from_string(value: String) -> Json.Deserializer:
return Json.Deserializer.new(value)
func deserialize_any(visitor: Visitor):
var next = _iter.peek()
if next.is_none(): return Result.err(Error.new("eof"))
match next.unwrap():
'n': return deserialize_nil(visitor)
't', 'f': return deserialize_bool(visitor)
'"': return deserialize_string(visitor)
'+', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
return deserialize_float(visitor)
'[': return deserialize_seq(visitor)
'{': return deserialize_dict(visitor)
var x: return Result.err(Error.new('unexpected character "%s" at position %s' % [x, 0]))
func deserialize_nil(visitor: Visitor):
_parser.skip_ws()
match _parser.str('null'):
var x when x.is_ok(): return Result.ok(visitor.visit_nil())
var x: return x
func deserialize_bool(visitor: Visitor):
_parser.skip_ws()
return _parser.any_of([_parser.str.bind('true'), _parser.str.bind('false')]).map(visitor.visit_bool)
func deserialize_int(visitor: Visitor):
_parser.skip_ws()
return _parser.parse_integer().chain(Convert.FromString.to_int).map(visitor.visit_int)
func deserialize_float(visitor: Visitor):
_parser.skip_ws()
return _parser.parse_float().chain(Convert.FromString.to_float).map(visitor.visit_float)
func deserialize_string(visitor: Visitor):
_parser.skip_ws()
return _parser.parse_string().map(visitor.visit_string)
func _deserialize_complex(open: Callable, close: Callable, on_success: Callable):
_parser.skip_ws()
var open_result = open.call()
if open_result.is_err(): return open_result
var value = on_success.call()
var close_result = close.call()
if close_result.is_err(): return close_result
return Result.ok(value)
func deserialize_seq(visitor: Visitor):
var open = _parser.char.bind('[')
var close = _parser.char.bind(']')
var visit = func() -> Variant:
return visitor.visit_seq(Json.SeqAccess.new(self))
return _deserialize_complex(open, close, visit)
func deserialize_dict(visitor: Visitor):
var open = _parser.char.bind('{')
var close = _parser.char.bind('}')
var visit = func() -> Variant:
return visitor.visit_dict(Json.MapAccess.new(self))
return _deserialize_complex(open, close, visit)
func deserialize_object(_name: StringName, _fields: Array[StringName], visitor: Visitor):
return deserialize_dict(visitor)

View file

@ -0,0 +1 @@
uid://cohemdkrxsa8o

228
godot/addons/serde/serde.gd Normal file
View file

@ -0,0 +1,228 @@
class_name Serde
const SerializeMethod = &"serialize"
const DeserializeMethod = &"deserialize"
enum SerdeType {
Nil,
Bool,
Int,
Float,
String,
Seq,
Dict,
Object
}
static func to_serde_type(godot_type: int) -> Result:
match godot_type:
TYPE_NIL: return Result.ok(Result.ok(SerdeType.Nil))
TYPE_BOOL: return Result.ok(Result.ok(SerdeType.Bool))
TYPE_INT: return Result.ok(Result.ok(SerdeType.Int))
TYPE_FLOAT: return Result.ok(SerdeType.Float)
TYPE_STRING: return Result.ok(SerdeType.String)
TYPE_STRING_NAME: return Result.ok(SerdeType.String)
TYPE_NODE_PATH: return Result.ok(SerdeType.String)
TYPE_VECTOR2: return Result.ok(SerdeType.Seq)
TYPE_VECTOR2I: return Result.ok(SerdeType.Seq)
TYPE_VECTOR3: return Result.ok(SerdeType.Seq)
TYPE_VECTOR3I: return Result.ok(SerdeType.Seq)
TYPE_VECTOR4I: return Result.ok(SerdeType.Seq)
TYPE_VECTOR4I: return Result.ok(SerdeType.Seq)
TYPE_RECT2: return Result.ok(SerdeType.Object)
TYPE_RECT2I: return Result.ok(SerdeType.Object)
TYPE_AABB: return Result.ok(SerdeType.Object)
TYPE_TRANSFORM2D: return Result.ok(SerdeType.Object)
TYPE_BASIS: return Result.ok(SerdeType.Object)
TYPE_TRANSFORM3D: return Result.ok(SerdeType.Object)
TYPE_PLANE: return Result.ok(SerdeType.Object)
TYPE_PROJECTION: return Result.ok(SerdeType.Object)
TYPE_QUATERNION: return Result.ok(SerdeType.Seq)
TYPE_COLOR: return Result.ok(SerdeType.Seq)
TYPE_RID: return Result.ok(SerdeType.Int)
TYPE_OBJECT: return Result.ok(SerdeType.Object)
TYPE_DICTIONARY: return Result.ok(SerdeType.Dict)
TYPE_ARRAY: return Result.ok(SerdeType.Seq)
TYPE_PACKED_BYTE_ARRAY: return Result.ok(SerdeType.Seq)
TYPE_PACKED_COLOR_ARRAY: return Result.ok(SerdeType.Seq)
TYPE_PACKED_INT32_ARRAY: return Result.ok(SerdeType.Seq)
TYPE_PACKED_INT64_ARRAY: return Result.ok(SerdeType.Seq)
TYPE_PACKED_STRING_ARRAY: return Result.ok(SerdeType.Seq)
TYPE_PACKED_FLOAT32_ARRAY: return Result.ok(SerdeType.Seq)
TYPE_PACKED_FLOAT64_ARRAY: return Result.ok(SerdeType.Seq)
TYPE_PACKED_VECTOR2_ARRAY: return Result.ok(SerdeType.Seq)
TYPE_PACKED_VECTOR3_ARRAY: return Result.ok(SerdeType.Seq)
TYPE_PACKED_VECTOR4_ARRAY: return Result.ok(SerdeType.Seq)
TYPE_SIGNAL: return Result.err("Refusing to (de)serialize Signal")
TYPE_CALLABLE: return Result.err("Refusing to (de)serialize Callable")
_: return Result.err(Error.InvalidArgument.new(godot_type))
static func _serialize_seq(serializer: Serializer, value: Variant) -> Variant:
var ser = serializer.serialize_seq(len(value))
for element in value:
ser.serialize_element(element)
return ser.end()
static func _serialize_dict(serializer: Serializer, value: Dictionary) -> Variant:
var ser: SerializeMap = serializer.serialize_dict(value.size())
for key in value.keys():
ser.serialize_entry(key, value[key])
return ser.end()
static func get_object_name(value: Variant) -> Option:
var name: Option = Option.none
if value.has_method("get_script"):
var script_name = value.get_script().get_global_name()
if script_name != null: name = Option.some(script_name)
if name == null and value.has_method("get_class"):
var native_name = value.get_class()
if native_name != null: name = Option.some(native_name)
return name
static func _serialize_object(serializer: Serializer, value: Variant) -> Variant:
var object_name = get_object_name(value).unwrap()
var ser = serializer.serialize_object(object_name, len(value.get_property_list()))
ser.serialize(value)
return ser.end()
static func default_serialize(serializer: Serializer, value: Variant, allow_eval: bool = false) -> Variant:
match to_serde_type(typeof(value)).unwrap():
SerdeType.Nil: return serializer.serialize_nil()
SerdeType.Bool: return serializer.serialize_bool(value)
SerdeType.Int: return serializer.serialize_int(value)
SerdeType.Float: return serializer.serialize_float(value)
SerdeType.String: return serializer.serialize_string(value)
SerdeType.Seq: return _serialize_seq(serializer, value)
SerdeType.Dict: return _serialize_dict(serializer, value)
SerdeType.Object: return _serialize_object(serializer, value)
return Result.err("could not deserialize type %s" % typeof(value))
static func serialize(serializer: Serializer, value: Variant, allow_eval: bool = false) -> Variant:
if typeof(value) == TYPE_OBJECT and value.has_method(SerializeMethod):
return value.call(serializer, value, allow_eval)
else:
return default_serialize(serializer, value, allow_eval)
class SerializeSeq extends RefCounted:
func serialize_element(element: Variant): Error.NotImplemented.raise('serialize_element')
func end(): Error.NotImplemented.raise('end')
class SerializeMap extends RefCounted:
func serialize_key(key: Variant): Error.NotImplemented.raise('serialize_key')
func serialize_value(value: Variant): Error.NotImplemented.raise('serialize_value')
func end(): Error.NotImplemented.raise('end')
func serialize_entry(key: Variant, value: Variant):
return Result.collect_ok([serialize_key(key), serialize_value(value)])
class SerializeObject extends RefCounted:
func serialize_field(name: StringName, value: Variant): Error.NotImplemented.raise('serialize_field')
func serialize(object: Variant): Error.NotImplemented.raise('serialize')
func end(): Error.NotImplemented.raise('end')
class Serializer extends RefCounted:
func serialize_nil(): Error.NotImplemented.raise('serialize_nil')
func serialize_bool(_value: bool): Error.NotImplemented.raise('serialize_bool')
func serialize_int(_value: int): Error.NotImplemented.raise('serialize_int')
func serialize_float(_value: float): Error.NotImplemented.raise('serialize_float')
func serialize_string(_value: String): Error.NotImplemented.raise('serialize_string')
func serialize_seq(_len: int): Error.NotImplemented.raise('serialize_string')
func serialize_dict(_len: int): Error.NotImplemented.raise('serialize_string')
func serialize_object(_name: StringName, _len: int): Error.NotImplemented.raise('serialize_string')
class Visitor extends RefCounted:
func visit_nil(): Error.NotImplemented.raise('visit_nil')
func visit_bool(_value: bool): Error.NotImplemented.raise('visit_bool')
func visit_int(_value: int): Error.NotImplemented.raise('visit_int')
func visit_float(_value: float): Error.NotImplemented.raise('visit_float')
func visit_string(_value: String): Error.NotImplemented.raise('visit_string')
func visit_seq(_access: SeqAccess): Error.NotImplemented.raise('visit_seq')
func visit_dict(_access: MapAccess): Error.NotImplemented.raise('visit_dict')
class GenericVisitor extends Visitor:
func visit_nil(): return null
func visit_bool(value: bool):
print('bool', value)
return value
func visit_int(value: int):
print('int', value)
return value
func visit_float(value: float):
print('float', value)
return value
func visit_string(value: String):
print('str', value)
return value
func visit_seq(access: SeqAccess):
print('seq', access)
return access.collect()
func visit_dict(access: MapAccess):
var a = access.collect()
print('dict', a)
return a
class AccessIterable extends Iterator:
var _next: Callable
func _init(next: Callable) -> void:
_next = next
func next() -> Option:
return _next.call().to_option().flatten()
class SeqAccess extends RefCounted:
func next_element(): Error.NotImplemented.raise('next_element')
func iterate() -> AccessIterable:
return AccessIterable.new(next_element)
func collect() -> Array:
var list = []
for item in iterate():
list.append(item)
return list
class MapAccess extends RefCounted:
func next_key(): Error.NotImplemented.raise('next_key')
func next_value(): Error.NotImplemented.raise('next_value')
func next_entry() -> Result:
var key = next_key()
var val = next_value()
return Result.collect_ok([key, val])
func iterate() -> AccessIterable:
return AccessIterable.new(next_entry)
func collect() -> Dictionary:
var dict: Dictionary = {}
print('collecting map')
for entry in iterate():
print('entry', entry)
dict.set(entry[0], entry[1])
return dict
class Deserializer extends RefCounted:
func deserialize_any(_visitor: Visitor): Error.NotImplemented.raise('deserialize_any')
func deserialize_nil(_visitor: Visitor): Error.NotImplemented.raise('deserialize_nil')
func deserialize_bool(_visitor: Visitor): Error.NotImplemented.raise('deserialize_bool')
func deserialize_int(_visitor: Visitor): Error.NotImplemented.raise('deserialize_int')
func deserialize_float(_visitor: Visitor): Error.NotImplemented.raise('deserialize_float')
func deserialize_string(_visitor: Visitor): Error.NotImplemented.raise('deserialize_string')
func deserialize_seq(_visitor: Visitor): Error.NotImplemented.raise('deserialize_seq')
func deserialize_dict(_visitor: Visitor): Error.NotImplemented.raise('deserialize_dict')
func deserialize_object(_name: StringName, _fields: Array[StringName], _visitor: Visitor): Error.NotImplemented.raise('deserialize_object')

View file

@ -0,0 +1 @@
uid://clfdg2pslks74

Binary file not shown.

View file

@ -0,0 +1,38 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://mii66yx7ep0m"
path="res://.godot/imported/Glock17.fbx-21a41422b406849d0ac156c344e49ded.scn"
[deps]
source_file="res://assets/Glock 17 Gen 4/Glock17.fbx"
dest_files=["res://.godot/imported/Glock17.fbx-21a41422b406849d0ac156c344e49ded.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1

Binary file not shown.

View file

@ -0,0 +1,53 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://bsamn3hu8mypn"
path="res://.godot/imported/MagazineOnly.blend-03c199d61b3da2f3a886fbf595d4fdb8.scn"
[deps]
source_file="res://assets/Glock 17 Gen 4/MagazineOnly.blend"
dest_files=["res://.godot/imported/MagazineOnly.blend-03c199d61b3da2f3a886fbf595d4fdb8.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
blender/nodes/visible=0
blender/nodes/active_collection_only=false
blender/nodes/punctual_lights=true
blender/nodes/cameras=true
blender/nodes/custom_properties=true
blender/nodes/modifiers=1
blender/meshes/colors=false
blender/meshes/uvs=true
blender/meshes/normals=true
blender/meshes/export_geometry_nodes_instances=false
blender/meshes/tangents=true
blender/meshes/skins=2
blender/meshes/export_bones_deforming_mesh_only=false
blender/materials/unpack_enabled=true
blender/materials/export_materials=1
blender/animation/limit_playback=true
blender/animation/always_sample=true
blender/animation/group_tracks=true

Binary file not shown.

View file

@ -0,0 +1,38 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://b1flnaqxpsntg"
path="res://.godot/imported/MagazineOnly.fbx-c5c1e9faa3577f6cfda078f5a38aa9af.scn"
[deps]
source_file="res://assets/Glock 17 Gen 4/MagazineOnly.fbx"
dest_files=["res://.godot/imported/MagazineOnly.fbx-c5c1e9faa3577f6cfda078f5a38aa9af.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=true
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
fbx/importer=0
fbx/allow_geometry_helper_nodes=false
fbx/embedded_image_handling=1

Binary file not shown.

View file

@ -0,0 +1,53 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://clc8ic0w44pqb"
path="res://.godot/imported/Main.blend-949e4928fb46c2598347c692921a4209.scn"
[deps]
source_file="res://assets/Glock 17 Gen 4/Main.blend"
dest_files=["res://.godot/imported/Main.blend-949e4928fb46c2598347c692921a4209.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
blender/nodes/visible=0
blender/nodes/active_collection_only=false
blender/nodes/punctual_lights=true
blender/nodes/cameras=true
blender/nodes/custom_properties=true
blender/nodes/modifiers=1
blender/meshes/colors=false
blender/meshes/uvs=true
blender/meshes/normals=true
blender/meshes/export_geometry_nodes_instances=false
blender/meshes/tangents=true
blender/meshes/skins=2
blender/meshes/export_bones_deforming_mesh_only=false
blender/materials/unpack_enabled=true
blender/materials/export_materials=1
blender/animation/limit_playback=true
blender/animation/always_sample=true
blender/animation/group_tracks=true

Binary file not shown.

View file

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://5a023hrws3gx"
path="res://.godot/imported/Main.glb-313ef174cbfc4d23192b83a144919b40.scn"
[deps]
source_file="res://assets/Glock 17 Gen 4/Main.glb"
dest_files=["res://.godot/imported/Main.glb-313ef174cbfc4d23192b83a144919b40.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=1
gltf/embedded_image_handling=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View file

@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cnqli7mte5jsn"
path="res://.godot/imported/MainRender.png-809411c3f2bc242eb5c881659d87b16b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/Glock 17 Gen 4/MainRender.png"
dest_files=["res://.godot/imported/MainRender.png-809411c3f2bc242eb5c881659d87b16b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
godot/assets/level.glb Normal file

Binary file not shown.

View file

@ -0,0 +1,37 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://cwrgvwx3lfwf6"
path="res://.godot/imported/level.glb-dadcc56a2e5f0c7946f5dd2ed4295803.scn"
[deps]
source_file="res://assets/level.glb"
dest_files=["res://.godot/imported/level.glb-dadcc56a2e5f0c7946f5dd2ed4295803.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
_subresources={}
gltf/naming_version=1
gltf/embedded_image_handling=1

BIN
godot/assets/player.glb Normal file

Binary file not shown.

File diff suppressed because it is too large Load diff

1
godot/icon.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 994 B

37
godot/icon.svg.import Normal file
View file

@ -0,0 +1,37 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://djmxd4580q6xs"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

57
godot/project.godot Normal file
View file

@ -0,0 +1,57 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="signalis-eb"
run/main_scene="uid://dttyp3682enn7"
config/features=PackedStringArray("4.4", "Forward Plus")
config/icon="res://icon.svg"
[global_group]
persist=""
[input]
move_up={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null)
]
}
move_down={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null)
]
}
move_left={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"location":0,"echo":false,"script":null)
]
}
move_right={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"location":0,"echo":false,"script":null)
]
}
run={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194325,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}
interact={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":69,"key_label":0,"unicode":101,"location":0,"echo":false,"script":null)
]
}
[layer_names]
3d_physics/layer_2="interaction"

16
godot/rust.gdextension Normal file
View file

@ -0,0 +1,16 @@
[configuration]
entry_symbol = "gdext_rust_init"
compatibility_minimum = 4.1
reloadable = true
[libraries]
linux.debug.x86_64 = "res://../rust/target/debug/libsignalis_rs.so"
linux.release.x86_64 = "res://../rust/target/release/libsignalis_rs.so"
windows.debug.x86_64 = "res://../rust/target/debug/signalis_rs.dll"
windows.release.x86_64 = "res://../rust/target/release/signalis_rs.dll"
macos.debug = "res://../rust/target/debug/libsignalis_rs.dylib"
macos.release = "res://../rust/target/release/libsignalis_rs.dylib"
macos.debug.arm64 = "res://../rust/target/debug/libsignalis_rs.dylib"
macos.release.arm64 = "res://../rust/target/release/libsignalis_rs.dylib"

View file

@ -0,0 +1 @@
uid://vb34isrum50a

80
godot/scenes/level.tscn Normal file
View file

@ -0,0 +1,80 @@
[gd_scene load_steps=9 format=3 uid="uid://dttyp3682enn7"]
[ext_resource type="PackedScene" uid="uid://crbrniwi6kd3p" path="res://scenes/player.tscn" id="1_2q6dc"]
[ext_resource type="Script" uid="uid://ds8lef4lc6xuj" path="res://src/door.gd" id="2_w8frs"]
[ext_resource type="Script" uid="uid://dyghf5fq7s72x" path="res://src/interactable.gd" id="3_w8frs"]
[ext_resource type="Script" uid="uid://pdpejp2xor23" path="res://src/persistence.gd" id="4_mx8sn"]
[sub_resource type="PlaneMesh" id="PlaneMesh_rd3vj"]
[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_w7c3h"]
[sub_resource type="BoxMesh" id="BoxMesh_w8frs"]
[sub_resource type="BoxShape3D" id="BoxShape3D_mx8sn"]
size = Vector3(2, 2.5, 0.5)
[node name="Node3D" type="Node3D"]
[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
transform = Transform3D(50, 0, 0, 0, 50, 0, 0, 0, 50, 0, 0, 0)
mesh = SubResource("PlaneMesh_rd3vj")
[node name="StaticBody3D" type="StaticBody3D" parent="MeshInstance3D"]
[node name="CollisionShape3D" type="CollisionShape3D" parent="MeshInstance3D/StaticBody3D"]
shape = SubResource("WorldBoundaryShape3D_w7c3h")
[node name="Player" parent="." instance=ExtResource("1_2q6dc")]
[node name="Camera3D" type="Camera3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 0.866025, 0.5, 0, -0.5, 0.866025, 0, 10, 15.3526)
projection = 1
size = 15.0
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(1, 0, 0, 0, -4.37114e-08, 1, 0, -1, -4.37114e-08, 0, 10, 0)
[node name="Door" type="StaticBody3D" parent="."]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 5)
collision_layer = 3
script = ExtResource("2_w8frs")
[node name="MeshInstance3D" type="MeshInstance3D" parent="Door"]
transform = Transform3D(2, 0, 0, 0, 2.5, 0, 0, 0, 0.5, 0, 1.25, 0)
mesh = SubResource("BoxMesh_w8frs")
[node name="CollisionShape3D" type="CollisionShape3D" parent="Door"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.25, 0)
shape = SubResource("BoxShape3D_mx8sn")
[node name="Interactable" type="Node3D" parent="Door"]
script = ExtResource("3_w8frs")
[node name="Control" type="Control" parent="."]
layout_mode = 3
anchors_preset = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="Persistence" type="Node" parent="Control"]
script = ExtResource("4_mx8sn")
metadata/_custom_type_script = "uid://pdpejp2xor23"
[node name="HBoxContainer" type="HBoxContainer" parent="Control"]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="SaveButton" type="Button" parent="Control/HBoxContainer"]
layout_mode = 2
text = "Save"
[node name="LoadButton" type="Button" parent="Control/HBoxContainer"]
layout_mode = 2
text = "Load
"
[connection signal="interacted" from="Door/Interactable" to="Door" method="_on_interact"]
[connection signal="pressed" from="Control/HBoxContainer/SaveButton" to="Control/Persistence" method="save"]
[connection signal="pressed" from="Control/HBoxContainer/LoadButton" to="Control/Persistence" method="load"]

143
godot/scenes/player.tscn Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,5 @@
[gd_scene load_steps=2 format=3 uid="uid://dpmbimh6m4ari"]
[ext_resource type="PackedScene" uid="uid://05nocsdvnsy5" path="res://assets/player.glb" id="1_mvwvq"]
[node name="Mesh" instance=ExtResource("1_mvwvq")]

8
godot/src/door.gd Normal file
View file

@ -0,0 +1,8 @@
class_name Door extends StaticBody3D
const Interactor = preload("res://src/interactor.gd")
@export var locked: bool = false
func _on_interact(_interactor: Interactor):
print('interacting!! with the door!!')

1
godot/src/door.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://ds8lef4lc6xuj

1
godot/src/file.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://xwi0ehjmeqdu

View file

@ -0,0 +1,7 @@
extends Node3D
const Interactor = preload("res://src/interactor.gd")
signal interacted(interactor: Interactor)
func interact(interactor: Node):
interacted.emit(interactor)

View file

@ -0,0 +1 @@
uid://dyghf5fq7s72x

72
godot/src/interactor.gd Normal file
View file

@ -0,0 +1,72 @@
extends Node3D
const Interactable = preload("res://src/interactable.gd")
const EnterSignals: Array[String] = ["area_entered", "body_entered"]
const ExitSignals: Array[String] = ["area_exited", "body_exited"]
@export var area: Area3D
class InteractableNode:
var _body: CollisionObject3D
var _interactable: Interactable
var global_position: Vector3:
get: return _body.global_position
func _init(body: CollisionObject3D, interactable: Interactable):
_body = body
_interactable = interactable
func interact(interactor: Node):
_interactable.interact(interactor)
func squared_distance_to(b: InteractableNode) -> float:
return global_position.distance_squared_to(b.global_position)
var interactables: Array[InteractableNode] = []
var _sorted: bool = false
func _ready() -> void:
_connect_many(area, EnterSignals, _on_object_entered)
_connect_many(area, ExitSignals, _on_object_exited)
func _connect_many(node: Node, signals: Array[String], fn: Callable):
for signal_name in signals:
node.connect(signal_name, fn)
func _on_object_entered(object: CollisionObject3D):
_sorted = false
var child = NodeExt.find_child_variant(object, Interactable)
if child.is_some():
interactables.append(InteractableNode.new(object, child.unwrap() as Interactable))
func is_same_object(object: CollisionObject3D, node: InteractableNode) -> bool:
return object == node._body
func _on_object_exited(object: CollisionObject3D):
var index = interactables.find_custom(
func(node): return is_same_object(object, node)
)
if index >= 0:
interactables.remove_at(index)
func _distance_from(node: InteractableNode) -> float:
return global_position.distance_squared_to(node.global_position)
func _distance_compare(a: InteractableNode, b: InteractableNode):
return _distance_from(a) > _distance_from(b)
func _sort_by_distance():
interactables.sort_custom(_distance_compare)
_sorted = true
func interact(interactable: InteractableNode):
interactable.interact(self)
func interact_nearest():
if not _sorted:
_sort_by_distance()
if len(interactables) > 0:
return interact(interactables[0])

View file

@ -0,0 +1 @@
uid://c3wlcxy4vnm2i

40
godot/src/node_ext.gd Normal file
View file

@ -0,0 +1,40 @@
class_name NodeExt
extends Object
class NodeChildIterator:
var _node: Node
var _include_internal: bool
var _index: int
var _length: int
func _init(node: Node, include_internal: bool) -> void:
_node = node
_include_internal = include_internal
_index = 0
_length = node.get_child_count(include_internal)
func _continue() -> bool:
return (_index < _length)
func _iter_init(_iter: Array) -> bool:
_index = 0
return _continue()
func _iter_next(_iter: Array) -> bool:
_index += 1
return _continue()
func _iter_get(_iter: Variant) -> Variant:
return _node.get_child(_index, _include_internal)
static func find_child(node: Node, predicate: Callable, include_internal: bool = false) -> Option:
for child in NodeChildIterator.new(node, include_internal):
if predicate.call(child):
return Option.some(child)
return Option.none
static func find_child_variant(node: Node, variant: Variant, include_internal: bool = false) -> Option:
for child in NodeChildIterator.new(node, include_internal):
if is_instance_of(child, variant):
return Option.some(child)
return Option.none

View file

@ -0,0 +1 @@
uid://bi44258j42pt4

86
godot/src/persistence.gd Normal file
View file

@ -0,0 +1,86 @@
class_name Persistence extends Node
@export var path = "user://saves/data.sav"
@export var group_name = "persist"
const PersistenceOptionsMethod = "_get_persistence_options"
const SaveMethod = "on_save"
const OnBeforeLoadMethod = "on_before_load"
const LoadMethod = "on_load"
# TODO: binary (de)serialization
static func get_instance_data(node: Node):
return {
path = node.get_path(),
scene = node.scene_file_path,
data = JSON.from_native(node.call(SaveMethod))
}
func save() -> Result:
DirAccess.make_dir_recursive_absolute(path.get_base_dir())
var file = FileAccess.open(path, FileAccess.WRITE)
var nodes = get_tree().get_nodes_in_group(group_name)
for node in nodes:
if node.scene_file_path.is_empty():
push_warning('"%s" is not instanced, skipping' % node.name)
continue
if not node.has_method("on_save"):
push_warning('"%s" does not have a "%s" method, skipping' % [node.name, SaveMethod])
continue
var data = get_instance_data(node)
file.store_line(Json.stringify(data))
return Result.Unit
func _get_or_add(instance_data: Dictionary) -> Variant:
var node_path: NodePath = instance_data['path']
var scene_path = instance_data['scene']
var node = get_node_or_null(node_path)
if node.is_queued_for_deletion():
await node.tree_exited
node = null
if node == null:
node = load(scene_path).instantiate()
var parent = node_path.slice(0, node_path.get_name_count() - 1)
get_node(parent).call_deferred('add_child', node)
return node
func load(fail_on_err: bool = false) -> Result:
if not FileAccess.file_exists(path):
return Result.err('ENOENT: "%s" does not exist' % path)
get_tree().call_group(group_name, OnBeforeLoadMethod)
var file = FileAccess.open(path, FileAccess.READ)
var length = file.get_length()
while file.get_position() < length:
var line = file.get_line()
var result = Json.parse_string(line)
if result.is_err():
if fail_on_err: return result
else:
push_error('could not read save entry. error: "%s"' % result.unwrap_err())
continue
var save_data = result.unwrap()
var instance = await _get_or_add(save_data)
if not instance.has_method(LoadMethod):
push_warning('"%s" does not have a "%s" method' % [instance.name, LoadMethod])
instance.queue_free()
continue
var instance_data = JSON.to_native(save_data.data)
instance.call(LoadMethod, instance_data)
return Result.Unit

View file

@ -0,0 +1 @@
uid://pdpejp2xor23

38
godot/src/player.gd Normal file
View file

@ -0,0 +1,38 @@
class_name Player extends CharacterBody3D
@export var walk_speed: float = 4.0
@export var run_speed: float = 6.0
@onready var input = $Input
@onready var interactor = $Interactor
var is_carrying_item = false
func _ready() -> void:
input.connect("interact", _on_interact)
func _physics_process(delta: float) -> void:
move_and_rotate(delta)
func move_and_rotate(_delta: float):
var speed = run_speed if input.is_running else walk_speed
velocity = input.next_velocity(speed)
if !velocity.is_zero_approx():
look_at(global_position + velocity, Vector3.UP, true)
move_and_slide()
func _on_interact():
interactor.interact_nearest()
func on_save():
return {
position = position,
rotation = rotation
}
func on_before_load():
pass
func on_load(data: Dictionary):
position = data.position
rotation = data.rotation

1
godot/src/player.gd.uid Normal file
View file

@ -0,0 +1 @@
uid://50vv0ta67tgl

28
godot/src/player_input.gd Normal file
View file

@ -0,0 +1,28 @@
class_name PlayerInput extends Node
signal run
signal interact
var is_running: bool = false
var is_interacting: bool = false
var movement_dir: Vector2:
get: return Input.get_vector(
'move_left', 'move_right',
'move_up', 'move_down'
).normalized()
func _process(_delta: float) -> void:
var running = Input.is_action_pressed("run")
if running != is_running:
run.emit(running)
is_running = running
var was_interacting = is_interacting
is_interacting = Input.is_action_pressed("interact")
if is_interacting and not was_interacting:
interact.emit()
func next_velocity(speed: float, dir: Vector2 = movement_dir) -> Vector3:
return Vector3(dir.x, 0, dir.y) * speed

View file

@ -0,0 +1 @@
uid://cngjp2wws4ld2

1
rust/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

261
rust/Cargo.lock generated Normal file
View file

@ -0,0 +1,261 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "gdextension-api"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ec0a03c8f9c91e3d8eb7ca56dea81c7248c03826dd3f545f33cd22ef275d4d1"
[[package]]
name = "glam"
version = "0.30.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b46b9ca4690308844c644e7c634d68792467260e051c8543e0c7871662b3ba7"
[[package]]
name = "godot"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a39ac85e71bb02a09badd538b76e11945bf7ee5e02c30d46e38e7d3dac33763"
dependencies = [
"godot-core",
"godot-macros",
]
[[package]]
name = "godot-bindings"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619afb7eda5a0f3e496d651b858ec7c03acf483a7cd36b7c4a7c0df96ae1a50e"
dependencies = [
"gdextension-api",
]
[[package]]
name = "godot-cell"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c198c3760f5f2edb852ebdbcc83f948fa1436d6deb2000bbdadb99fc6858bfd"
[[package]]
name = "godot-codegen"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7673ba4ef6a69205dc93909c5c5fc15f3c9159e1829b1ff1e7c4a94ba31f93f3"
dependencies = [
"godot-bindings",
"heck",
"nanoserde",
"proc-macro2",
"quote",
"regex",
]
[[package]]
name = "godot-core"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1124ea2c9753d593691d16864edb57552f9321333a286f802932e3d8a56d70f3"
dependencies = [
"glam",
"godot-bindings",
"godot-cell",
"godot-codegen",
"godot-ffi",
]
[[package]]
name = "godot-ffi"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec84cddb0e58a8ad3639e88ba1f06a8350ddff4cc368a7d78c1cff1b46e7ff0a"
dependencies = [
"godot-bindings",
"godot-codegen",
"godot-macros",
"libc",
]
[[package]]
name = "godot-macros"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a41150c0d205c7b671402a3f06f1b89910d90c513a0442fa4b2b8ea6665969"
dependencies = [
"godot-bindings",
"libc",
"proc-macro2",
"quote",
"venial",
]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "libc"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "nanoserde"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a36fb3a748a4c9736ed7aeb5f2dfc99665247f1ce306abbddb2bf0ba2ac530a4"
dependencies = [
"nanoserde-derive",
]
[[package]]
name = "nanoserde-derive"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a846cbc04412cf509efcd8f3694b114fc700a035fb5a37f21517f9fb019f1ebc"
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "signalis-rs"
version = "0.1.0"
dependencies = [
"godot",
"serde",
"serde_json",
]
[[package]]
name = "syn"
version = "2.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "venial"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a42528baceab6c7784446df2a10f4185078c39bf73dc614f154353f1a6b1229"
dependencies = [
"proc-macro2",
"quote",
]

12
rust/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
name = "signalis-rs"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["cdylib"]
[dependencies]
godot = "0.3.0"
serde = "1.0.219"
serde_json = "1.0.140"

8
rust/src/lib.rs Normal file
View file

@ -0,0 +1,8 @@
use godot::prelude::*;
mod persist;
struct GodotExtension;
#[gdextension]
unsafe impl ExtensionLibrary for GodotExtension {}

11
rust/src/persist.rs Normal file
View file

@ -0,0 +1,11 @@
use godot::prelude::*;
#[derive(GodotClass)]
pub struct Persist;
#[godot_api]
impl IRefCounted for Persist {
fn init(base: Base<RefCounted>) -> Self {
Persist
}
}