Files
airplane-mode/addons/godot_colyseus/lib/schema.gd

309 lines
7.8 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends "./schema_interface.gd"
const col = preload("res://addons/godot_colyseus/lib/collections.gd")
const OP = preload("res://addons/godot_colyseus/lib/operations.gd")
const TypeInfo = preload("res://addons/godot_colyseus/lib/type_info.gd")
const END_OF_STRUCTURE = 0xc1
const NIL = 0xc0
const INDEX_CHANGE = 0xd4
const Decoder = preload("res://addons/godot_colyseus/lib/decoder.gd")
const EventListener = preload("res://addons/godot_colyseus/lib/listener.gd")
const SchemaInterface = preload("res://addons/godot_colyseus/lib/schema_interface.gd")
class Field:
const Types = preload("res://addons/godot_colyseus/lib/types.gd")
var index: int
var name: String
var value
var current_type: TypeInfo
func _init(name: String,type: String,schema_type = null):
current_type = TypeInfo.new(type)
if schema_type is String:
current_type.sub_type = TypeInfo.new(schema_type)
elif schema_type is GDScript:
if type == Types.REF:
current_type.sub_type = schema_type
else:
current_type.sub_type = TypeInfo.new(Types.REF, schema_type)
elif schema_type is TypeInfo:
current_type.sub_type = schema_type
self.name = name
func _to_string():
if current_type:
return current_type.to_string()
else:
return 'null'
var _fields: Array = []
var _field_index = {}
var _refs = {}
var _change_listeners = {}
func _get_property_list():
var result = []
for field in _fields:
result.append({
name = field.name,
type = Types.to_gd_type(field.current_type.type),
usage = PROPERTY_USAGE_DEFAULT
})
return result
func _get(property):
if _field_index.has(property):
var value = _field_index[property].value
if value is SchemaInterface:
pass
return value
return null
func _set(property, value):
if _field_index.has(property):
var field = _field_index[property]
var old = field.value
if old is SchemaInterface:
pass
field.value = value
return true
return false
# [event: String, target, key_or_index]
# path format {path}:{action}
# {action} is one of:
# add Create sub object, paramaters [current, new_value, key]
# remove Delete sub object paramaters [current, old_value, key]
# replace Replace sub object paramaters [current, new_value, key]
# delete Current object is deleted, paramaters [current]
# create Current object is created, paramaters [current]
# change Current object's attributes has changed, paramaters [current]
# clear Current Array or Map has cleared, paramaters [current]
func listen(path: String) -> EventListener:
if not _change_listeners.has(path):
_change_listeners[path] = EventListener.new()
return _change_listeners[path]
static func define_fields() -> Array:
return []
func _init():
_fields = self.get_script().define_fields()
var counter = 0
for field in _fields:
field.index = counter
_setup_field(field)
counter += 1
func _setup_field(field: Field):
_field_index[field.name] = field
var type = field.current_type
match type.type:
Types.MAP:
assert(type.sub_type != null) #,"Schema type is requested")
Types.ARRAY:
assert(type.sub_type != null) #,"Schema type is requested")
field.value = col.Collection.new()
Types.SET:
assert(type.sub_type != null) #,"Schema type is requested")
Types.COLLECTION:
assert(type.sub_type != null) #,"Schema type is requested")
Types.REF:
assert(type.sub_type != null) #,"Schema type is requested")
Types.NUMBER, Types.FLOAT32, Types.FLOAT64:
field.value = 0.0
Types.INT8, Types.UINT8, Types.INT16, Types.UINT16, Types.INT32, Types.UINT32, Types.INT64, Types.UINT64:
field.value = 0
Types.STRING:
field.value = ""
func get_fields():
return _fields
func decode(decoder: Decoder) -> int:
var ref_id = 0
var ref: Ref = Ref.new(self, TypeInfo.new(Types.REF))
_refs[ref_id] = ref
var changes = []
var changed_objects = {}
while decoder.has_more():
var byte = decoder.reader.get_u8()
if byte == OP.SWITCH_TO_STRUCTURE:
ref_id = decoder.number()
var next_ref = _refs[ref_id]
assert(next_ref != null) #,str('"refId" not found:', ref_id))
ref = next_ref
continue
var is_schema = ref.type_info.type == Types.REF
var operation = byte
if is_schema:
operation = (byte >> 6) << 6
if operation == OP.CLEAR:
ref.value.clear(true)
if ref.value is SchemaInterface:
changes.append({
target = ref.value,
event = "clear",
argv = []
})
continue
var field_index = byte % _re_replace(operation)
if not is_schema:
field_index = decoder.number()
var ref_value = ref.value
if ref_value is SchemaInterface:
var old = ref_value.meta_get(field_index)
var new
var key = field_index
if ref.type_info.type != Types.MAP:
key = ref_value.meta_get_key(field_index)
if operation == OP.DELETE:
if ref.type_info.type == Types.MAP:
key = ref_value.meta_get_key(field_index)
ref_value.meta_remove(field_index)
else:
if ref.type_info.type == Types.MAP:
key = decoder.read_utf8()
var type: TypeInfo = ref_value.meta_get_subtype(field_index)
if type.is_schema_type():
var new_ref_id = decoder.number()
if _refs.has(new_ref_id):
new = _refs[new_ref_id].value
else:
if operation != OP.REPLACE:
new = type.create()
new.id = new_ref_id
_refs[new_ref_id] = Ref.new(new, type)
else:
new = type.decode(decoder)
if old != new:
if old == null:
changes.append({
target = ref_value,
event = "add",
argv = [new, key]
})
elif new == null:
changes.append({
target = ref_value,
event = "remove",
argv = [old, key]
})
else:
changes.append({
target = ref_value,
event = "replace",
argv = [new, key]
})
if old != null:
if old is SchemaInterface && old.id != null:
changes.append({
target = old,
event = "delete",
argv = []
})
_refs.erase(old.id)
if new != null:
ref_value.meta_set(field_index, key, new)
if new is SchemaInterface:
changes.append({
target = new,
event = "create",
argv = []
})
new.set_parent(ref_value, field_index)
elif old != null:
ref_value.meta_remove(field_index)
changed_objects[ref_value] = true
for change in changes:
var target = change.target
target.trigger(change.event, change.argv)
for target in changed_objects.keys():
target.trigger("change", [])
return 0
func _re_replace(operation):
if operation == OP.REPLACE:
return 255
return operation
func clear(decoding: bool = false):
pass
func meta_get(index):
assert(_fields.size() > index)
var field : Field = _fields[index]
return field.value
func meta_get_key(index):
assert(_fields.size() > index)
var field : Field = _fields[index]
return field.name
func meta_get_subtype(index):
assert(_fields.size() > index)
var field : Field = _fields[index]
return field.current_type
func meta_set(index, key, value):
assert(_fields.size() > index)
var field : Field = _fields[index]
field.value = value
func meta_remove(index):
assert(_fields.size() > index)
var field : Field = _fields[index]
var old = field.value
field.value = null
return old
func _to_string():
var obj = to_object()
return JSON.stringify(obj)
func trigger(event: String, argv: Array = [], path: PackedStringArray = PackedStringArray(), target: Object = self):
var path_copy = PackedStringArray(path)
path_copy.reverse()
var path_str = '/'.join(path_copy) + ":" + event
if _change_listeners.has(path_str):
var ls: EventListener = _change_listeners[path_str]
argv.insert(0, target)
ls.emit(argv)
else:
super.trigger(event, argv, path, target)
func to_object():
var dic = {}
for field in _fields:
if field.value is SchemaInterface:
dic[field.name] = field.value.to_object()
else:
dic[field.name] = field.value
return dic