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