Initial commit for testing/fixing a deprecated godot-colyseus addon

This commit is contained in:
2026-01-10 11:12:02 -05:00
commit 081a5fcf1a
76 changed files with 3456 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bdhx6b3h4tkao"
path="res://.godot/imported/blur.png-4563c8475376415b661c69b2b9f1d2d4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://addons/godot_colyseus/demo/blur.png"
dest_files=["res://.godot/imported/blur.png-4563c8475376415b661c69b2b9f1d2d4.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

View File

@@ -0,0 +1,8 @@
[gd_scene load_steps=2 format=2]
[ext_resource path="res://addons/godot_colyseus/demo/blur.png" type="Texture2D" id=1]
[node name="char" type="Sprite2D"]
position = Vector2( 0, -16 )
scale = Vector2( 0.6, 1 )
texture = ExtResource( 1 )

View File

@@ -0,0 +1,32 @@
extends Control
const colyseus = preload("res://addons/godot_colyseus/lib/colyseus.gd")
var room: colyseus.Room
func _ready():
var client = colyseus.Client.new("ws://localhost:2567")
var promise = client.join_or_create(colyseus.Schema, "chat")
await promise.completed
if promise.get_state() == promise.State.Failed:
print("Failed")
return
var room: colyseus.Room = promise.get_data()
room.on_message("messages").on(Callable(self, "_on_messages"))
$label.text += "Connected"
self.room = room
# Called every frame. 'delta' is the elapsed time since the previous frame.
#func _process(delta):
# pass
func _on_messages(data):
$label.text += "\n" + data
func _on_send_pressed():
if $input.text.is_empty():
return
room.send("message", $input.text)
$input.text = ""

View File

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

View File

@@ -0,0 +1,48 @@
[gd_scene load_steps=2 format=3 uid="uid://dpxco2ygtk2bx"]
[ext_resource type="Script" path="res://addons/godot_colyseus/demo/chat.gd" id="1"]
[node name="Control" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1")
[node name="input" type="TextEdit" parent="."]
layout_mode = 1
anchors_preset = -1
anchor_top = 0.927
anchor_right = 1.0
anchor_bottom = 1.0
offset_top = -0.200012
offset_right = -110.0
[node name="send" type="Button" parent="."]
layout_mode = 1
anchors_preset = 3
anchor_left = 1.0
anchor_top = 1.0
anchor_right = 1.0
anchor_bottom = 1.0
offset_left = -104.0
offset_top = -42.0
offset_right = 1.0
offset_bottom = -4.0
grow_horizontal = 0
grow_vertical = 0
text = "Send"
[node name="label" type="Label" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
offset_right = -2.0
offset_bottom = -54.0
grow_horizontal = 2
grow_vertical = 2
[connection signal="pressed" from="send" to="." method="_on_send_pressed"]

View File

@@ -0,0 +1,27 @@
[gd_scene load_steps=2 format=3 uid="uid://cxtq2mh35wwc5"]
[sub_resource type="GDScript" id="GDScript_uosag"]
script/source = "extends Control
# Called when the node enters the scene tree for the first time.
func _ready():
print(\"test \", await test_await())
# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
pass
func test_await():
return 2
"
[node name="Control" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = SubResource("GDScript_uosag")

View File

@@ -0,0 +1,68 @@
extends Node2D
const colyseus = preload("res://addons/godot_colyseus/lib/colyseus.gd")
const Char = preload("./char.tscn")
class Player extends colyseus.Schema:
static func define_fields():
return [
colyseus.Field.new("x", colyseus.NUMBER),
colyseus.Field.new("y", colyseus.NUMBER)
]
var node
func _to_string():
return str("(",self.x,",",self.y,")")
class RoomState extends colyseus.Schema:
static func define_fields():
return [
colyseus.Field.new("players", colyseus.MAP, Player),
]
var room: colyseus.Room
# Called when the node enters the scene tree for the first time.
func _ready():
var client = colyseus.Client.new("ws://localhost:2567")
var promise = client.join_or_create(RoomState, "state_handler")
await promise.completed
if promise.get_state() == promise.State.Failed:
print("Failed")
return
var room: colyseus.Room = promise.get_data()
var state: RoomState = room.get_state()
state.listen('players:add').on(Callable(self, "_on_players_add"))
room.on_state_change.on(Callable(self, "_on_state"))
room.on_message("hello").on(Callable(self, "_on_message"))
self.room = room
func _on_message(data):
print(str("hello:", data))
func _on_state(state):
pass
func _on_players_add(target, value, key):
print("Add:", " key:", key, " ", value)
var ch = Char.instantiate()
ch.position = Vector2(value.x, value.y)
add_child(ch)
value.node = ch
value.listen(":change").on(Callable(self, "_on_player"))
func _on_player(target):
print("Change ", target)
var ch = target.node
ch.position = Vector2(target.x, target.y)
func _physics_process(delta):
if Input.is_action_pressed("ui_up"):
room.send("move", { y = -1 });
elif Input.is_action_pressed("ui_down"):
room.send("move", { y = 1 });
elif Input.is_action_pressed("ui_left"):
room.send("move", { x = -1 });
elif Input.is_action_pressed("ui_right"):
room.send("move", { x = 1 });

View File

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

View File

@@ -0,0 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://ceqab8i8yqmjj"]
[ext_resource type="Script" path="res://addons/godot_colyseus/demo/main.gd" id="1"]
[node name="Node2D" type="Node2D"]
script = ExtResource("1")

View File

@@ -0,0 +1,10 @@
@tool
extends EditorPlugin
func _enter_tree():
pass
func _exit_tree():
pass

View File

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

View File

@@ -0,0 +1,132 @@
extends RefCounted
const promises = preload("res://addons/godot_colyseus/lib/promises.gd")
const Promise = promises.Promise;
const RunPromise = promises.RunPromise;
const HTTP = preload("res://addons/godot_colyseus/lib/http.gd")
const CRoom = preload("res://addons/godot_colyseus/lib/room.gd")
const RoomInfo = preload("res://addons/godot_colyseus/lib/room_info.gd")
var endpoint: String
func _init(endpoint: String):
self.endpoint = endpoint
func join_or_create(schema_type: GDScript, room_name: String, options: Dictionary = {}) -> Promise:
return RunPromise.new(
_create_match_make_request,
["joinOrCreate", room_name, options, schema_type])
func create(schema_type: GDScript, room_name: String, options: Dictionary = {}) -> Promise:
return RunPromise.new(
_create_match_make_request,
["create", room_name, options, schema_type])
func join(schema_type: GDScript, room_name: String, options: Dictionary = {}) -> Promise:
return RunPromise.new(
_create_match_make_request,
["join", room_name, options, schema_type])
func join_by_id(schema_type: GDScript, room_id: String, options: Dictionary = {}) -> Promise:
return RunPromise.new(
_create_match_make_request,
["joinById", room_id, options, schema_type])
func reconnect(schema_type: GDScript, reconnection_token: String) -> Promise:
var arr = reconnection_token.split(":")
if arr.size() == 2:
var room_id = arr[0]
var token = arr[1]
return RunPromise.new(
_create_match_make_request,
["reconnect", room_id, {"reconnectionToken": token}, schema_type])
else:
var fail = Promise.new()
fail.reject("Invalidate `reconnection_token`")
return fail
func get_available_rooms(room_name:String) -> Promise:
var path = "/matchmake/" + room_name
return RunPromise.new(
_http_get,
[path, {"Accept": "application/json"}]
).then(_process_rooms)
func _create_match_make_request(
promise: Promise,
method: String,
room_name: String,
options: Dictionary,
schema_type: GDScript):
var path: String = "/matchmake/" + method + "/" + room_name
var server = endpoint
if server.begins_with("ws"):
server = server.replace("ws", "http")
if options == null:
options = {}
var http = HTTP.new(server)
var req = HTTP.RequestInfo.new("POST", path)
req.add_header("Accept", "application/json")
req.add_header("Content-Type", "application/json")
req.body = options
var resp = http.fetch(req)
if resp.get_state() == Promise.State.Waiting:
await resp.completed
if resp.get_state() == Promise.State.Failed:
promise.reject(resp.get_error())
return
var res: HTTP.Response = resp.get_data()
var response = res.json()
if response == null:
promise.reject("Server unreadable!")
return
if response.get('code') != null:
promise.reject(response['error'])
return
var room = CRoom.new(response["room"]["name"], schema_type)
room.room_id = response["room"]["roomId"]
room.session_id = response["sessionId"]
room.connect_remote(_build_endpoint(response["room"], { "sessionId": room.session_id }))
room.on_join.once(Callable(self, "_room_joined"), [promise, room])
room.on_error.once(Callable(self, "_room_error"), [promise, room])
func _room_joined(promise: Promise, room: CRoom):
room.on_error.off(Callable(self, "_room_error"))
promise.resolve(room)
func _room_error(code: int, message: String, promise: Promise, room: CRoom):
promise.reject(str("[", code, "]", message))
func _build_endpoint(room: Dictionary, options: Dictionary = {}):
var params = PackedStringArray()
for name in options.keys():
params.append(name + "=" + options[name])
return endpoint + "/" + room["processId"] + "/" + room["roomId"] + "?" + "&".join(params)
func _http_get(promise: Promise, path: String, headers: Dictionary):
var server = endpoint
if server.begins_with("ws"):
server = server.replace("ws", "http")
var http = HTTP.new(server)
var req = HTTP.RequestInfo.new("GET", path)
for key in headers.keys():
req.add_header(key, headers[key])
var resp = http.fetch(req)
if resp.get_state() == Promise.State.Waiting:
await resp.completed
if resp.get_state() == Promise.State.Failed:
promise.reject(resp.get_error())
return
var res: HTTP.Response = resp.get_data()
promise.resolve(res.json())
func _process_rooms(result, promise: Promise):
var list = []
for data in result:
list.append(RoomInfo.new(data))
return list

View File

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

View File

@@ -0,0 +1,180 @@
extends Object
const EventListener = preload("res://addons/godot_colyseus/lib/listener.gd")
const SchemaInterface = preload("res://addons/godot_colyseus/lib/schema_interface.gd")
class Collection extends SchemaInterface:
var sub_type
func meta_get_subtype(index):
return sub_type
class ArraySchema extends Collection:
var items = []
func clear(decoding: bool = false):
items.clear()
func meta_get(index):
if items.size() > index:
return items[index]
return null
func meta_get_key(index):
return str(index)
func meta_set(index, key, value):
_set_item(index, value)
func meta_remove(index):
assert(items.size() > index)
items.remove_at(index)
func _set_item(index, value):
if items.size() > index:
items[index] = value
else:
while items.size() < index - 1:
items.append(null)
items.append(value)
func meta_set_self(value):
items = value
func at(index: int):
return items[index]
func size() -> int:
return items.size()
func _to_string():
return JSON.stringify(items)
func to_object():
return items
class MapSchema extends Collection:
var _keys = {}
var items = {}
var _counter = 0
func clear(decoding: bool = false):
items.clear()
_keys.clear()
_counter = 0
func meta_get(index):
if _keys.has(index):
return items[_keys[index]]
return null
func meta_get_key(index):
if not _keys.has(index):
return index
return _keys[index]
func meta_set(index, key, value):
_keys[index] = key
items[key] = value
func meta_remove(index):
if not _keys.has(index):
return
items.erase(_keys[index])
_keys.erase(index)
func at(key: String):
return items.get(key)
func put(key: String, value):
_keys[_counter] = key
items[key] = value
++_counter
func _to_string():
return JSON.stringify(items)
func to_object():
return items
func keys():
var list = []
for k in _keys:
list.append(_keys[k])
return list
func size():
return _keys.size()
func has(key: String):
return items.has(key)
class SetSchema extends Collection:
var _counter = 0
var items = {}
func clear(decoding: bool = false):
items.clear()
_counter = 0
func meta_get(index):
if items.size() > index:
return items[index]
return null
func meta_get_key(index):
return str(index)
func meta_set(index, key, value):
_set_item(index, value)
func meta_remove(index):
items.erase(index)
func _set_item(index, value):
if items.size() > index:
items[index] = value
else:
while items.size() < index - 1:
items.append(null)
items.append(value)
func _to_string():
return JSON.stringify(items)
func to_object():
return items
class CollectionSchema extends Collection:
var items = []
func clear(decoding: bool = false):
items.clear()
func meta_get(index):
if items.size() > index:
return items[index]
return null
func meta_get_key(index):
return str(index)
func meta_set(index, key, value):
_set_item(index, value)
func meta_remove(index):
items.erase(index)
func _set_item(index, value):
if items.size() > index:
items[index] = value
else:
while items.size() < index - 1:
items.append(null)
items.append(value)
func _to_string():
return JSON.stringify(items)
func to_object():
return items

View File

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

View File

@@ -0,0 +1,32 @@
extends Object
const Client = preload("res://addons/godot_colyseus/lib/client.gd")
const Schema = preload("res://addons/godot_colyseus/lib/schema.gd")
const Room = preload("res://addons/godot_colyseus/lib/room.gd")
const types = preload("res://addons/godot_colyseus/lib/types.gd")
const Field = Schema.Field
const REF = types.REF
const MAP = types.MAP
const ARRAY = types.ARRAY
const STRING = types.STRING
const NUMBER = types.NUMBER
const BOOLEAN = types.BOOLEAN
const INT8 = types.INT8
const UINT8 = types.UINT8
const INT16 = types.INT16
const UINT16 = types.UINT16
const INT32 = types.INT32
const UINT32 = types.UINT32
const INT64 = types.INT64
const UINT64 = types.UINT64
const FLOAT32 = types.FLOAT32
const FLOAT64 = types.UINT32
const collections = preload("res://addons/godot_colyseus/lib/collections.gd")
const ArraySchema = collections.ArraySchema
const MapSchema = collections.MapSchema
const SetSchema = collections.SetSchema
const CollectionSchema = collections.CollectionSchema
const RoomInfo = preload("res://addons/godot_colyseus/lib/room_info.gd")
const Promise = preload("res://addons/godot_colyseus/lib/promises.gd").Promise

View File

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

View File

@@ -0,0 +1,68 @@
extends RefCounted
const MsgPack = preload("res://addons/godot_colyseus/lib/msgpack.gd")
var reader: StreamPeerBuffer
func _init(reader: StreamPeerBuffer):
reader.big_endian = false
self.reader = reader
func read_utf8() -> String:
var prefix = reader.get_u8()
var length = -1
if prefix < 0xc0:
length = prefix & 0x1f
elif prefix == 0xd9:
length = reader.get_u8()
elif prefix == 0xda:
length = reader.get_u16()
elif prefix == 0xdb:
length = reader.get_u32()
return reader.get_utf8_string(length)
func number():
var prefix = reader.get_u8()
if prefix < 0x80:
return prefix
elif prefix == 0xca:
return reader.get_float()
elif prefix == 0xcb:
return reader.get_double()
elif prefix == 0xcc:
return reader.get_u8()
elif prefix == 0xcd:
return reader.get_u16()
elif prefix == 0xce:
return reader.get_u32()
elif prefix == 0xcf:
return reader.get_u64()
elif prefix == 0xd0:
return reader.get_8()
elif prefix == 0xd1:
return reader.get_16()
elif prefix == 0xd2:
return reader.get_32()
elif prefix == 0xd3:
return reader.get_64()
elif prefix > 0xdf:
return (0xff - prefix + 1) * -1
func has_more() -> bool:
return reader.get_position() < reader.get_size()
func current_bit() -> int:
return reader.data_array[reader.get_position()]
func is_number() -> bool:
var prefix = current_bit()
return prefix < 0x80 || (prefix >= 0xca && prefix <= 0xd3)
func unpack():
var result = MsgPack.decode(reader)
if result.error == OK:
return result.result
return null

View File

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

View File

@@ -0,0 +1,67 @@
extends RefCounted
const MsgPack = preload("res://addons/godot_colyseus/lib/msgpack.gd")
var writer: StreamPeerBuffer
func _init(writer: StreamPeerBuffer):
writer.big_endian = false
self.writer = writer
func string(v: String):
var bytes = v.to_utf8_buffer()
var length = bytes.size()
if length < 0x20:
writer.put_u8(length | 0xa0)
elif length < 0x100:
writer.put_u8(0xd9)
writer.put_u8(length)
elif length < 0x10000:
writer.put_u8(0xda)
writer.put_u16(length)
elif length < 0x100000000:
writer.put_u8(0xdb)
writer.put_u32(length)
else:
assert(false) #,"String too long")
writer.put_data(bytes)
func number(v):
if v == NAN:
return number(0)
elif v != abs(v):
writer.put_u8(0xcb)
writer.put_double(v)
elif v >= 0:
if v < 0x80:
writer.put_u8(v)
elif v < 0x100:
writer.put_u8(0xcc)
writer.put_u8(v)
elif v < 0x10000:
writer.put_u8(0xcd)
writer.put_u16(v)
elif v < 0x100000000:
writer.put_u8(0xce)
writer.put_u32(v)
else:
writer.put_u8(0xcf)
writer.put_u32(v)
else:
if v >= -0x20:
writer.put_u8(0xe0 | (v + 0x20))
elif v >= -0x80:
writer.put_u8(0xd0)
writer.put_8(v)
elif v >= -0x8000:
writer.put_u8(0xd1)
writer.put_16(v)
elif v >= -0x80000000:
writer.put_u8(0xd2)
writer.put_32(v)
else:
writer.put_u8(0xd3)
writer.put_64(v)

View File

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

View File

@@ -0,0 +1,23 @@
extends RefCounted
var _running = false
var fn: Callable
var argv: Array
# Called when the node enters the scene tree for the first time.
func _init(fn: Callable,argv: Array = []):
self.fn = fn
self.argv = argv
func start():
if not _running:
_running = true
var root: SceneTree = Engine.get_main_loop()
while true:
await root.process_frame
if not _running:
return
fn.callv(argv)
func stop():
_running = false

View File

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

View File

@@ -0,0 +1,229 @@
extends RefCounted
const promises = preload("res://addons/godot_colyseus/lib/promises.gd")
const Promise = promises.Promise
const RunPromise = promises.RunPromise
var _connected = false
var _client_promise: promises.Promise
class RequestInfo:
var method: String = "GET"
var path: String = "/"
var headers: PackedStringArray = []
var body
func _init(method: String = "GET",path: String = "/"):
self.method = method
self.path = path
func add_header(key: String, value):
headers.append(str(key, ": ", value))
return self
func http_method():
match method.to_upper():
"GET":
return HTTPClient.METHOD_GET
"POST":
return HTTPClient.METHOD_POST
"PUT":
return HTTPClient.METHOD_PUT
"DELETE":
return HTTPClient.METHOD_DELETE
"HEAD":
return HTTPClient.METHOD_HEAD
"OPTIONS":
return HTTPClient.METHOD_OPTIONS
func get_body():
if body == null:
body = ""
if body is Dictionary or body is Array:
body = JSON.stringify(body)
return body
class Response:
var _response_chunks: Array = []
var _body
var _has_response = false
var _status_code: int = 0
var _content_length: int = 0
var _headers
func body() -> PackedByteArray:
if _body == null:
_body = PackedByteArray()
for chunk in _response_chunks:
_body.append_array(chunk)
return _body
func text() -> String:
return body().get_string_from_utf8()
func json():
var test_json_conv = JSON.new()
var err = test_json_conv.parse(text())
if err == OK:
return test_json_conv.get_data()
print(str(test_json_conv.get_error_message(), ":", test_json_conv.get_error_line()))
return null
func headers() -> Dictionary:
return _headers
func status_code() -> int:
return _status_code
func content_length() -> int:
return _content_length
func _to_string():
var lines = PackedStringArray()
lines.append(str("StatusCode: ", status_code()))
lines.append(str("ContentLength: ", content_length()))
lines.append(str("Headers: "))
var header = headers()
for key in header.keys():
lines.append(str(" ", key, ": ", header[key]))
lines.append(str("Body: [", body().size(), "]"))
return "\n".join(lines)
var _old_status
func _init(server: String):
var url = parseUrl(server)
_client_promise = promises.RunPromise.new(Callable(self, "_setup"), [url.host, url.port, url.ssl]);
static func parseUrl(url) -> Dictionary:
var regex = RegEx.new()
regex.compile("(\\w+):\\/\\/([^\\/:]+)(:(\\d+))?")
var result = regex.search(url)
var scheme = result.get_string(1)
var ssl = scheme == "https"
var host = result.get_string(2)
var portstr = result.get_string(4)
var port = -1
if portstr != "":
port = int(portstr)
return {
"host": host,
"ssl": ssl,
"port": port,
}
var host: String
func _setup(promise: promises.Promise, host, port, ssl):
var client = HTTPClient.new()
self.host = host
var error = client.connect_to_host(host, port, TLSOptions.client() if ssl else null)
if error != OK:
promise.reject(str("ErrorCode: ", error))
var root = Engine.get_main_loop()
while true:
await root.process_frame
client.poll()
var status = client.get_status()
match status:
HTTPClient.STATUS_CONNECTED:
promise.resolve(client)
break
HTTPClient.STATUS_DISCONNECTED:
promise.reject("Disconnected from Host")
break
HTTPClient.STATUS_CANT_CONNECT:
promise.reject("Can't Connect to Host")
break
func _request(promise: Promise, request: RequestInfo):
if _client_promise.get_state() == promises.Promise.State.Waiting:
await _client_promise.completed
if _client_promise.get_state() == Promise.State.Failed:
promise.reject(_client_promise.get_error())
return
var client: HTTPClient = _client_promise.get_data()
var body = request.get_body()
var error
if body is String:
error = client.request(request.http_method(), request.path, request.headers, body)
elif body is PackedByteArray:
error = client.request_raw(request.http_method(), request.path, request.headers, body)
else:
promise.reject("Unsupport body type")
return
if error != OK:
promise.reject(str("Error code ", error))
return
var root = Engine.get_main_loop()
var response = Response.new()
while true:
await root.process_frame
error = client.poll()
var status = client.get_status()
match status:
HTTPClient.STATUS_DISCONNECTED:
if response._has_response:
promise.resolve(response)
else:
promise.reject("Disconnected from Host")
return
HTTPClient.STATUS_CANT_CONNECT:
promise.reject("Can't Connect to Host")
return
HTTPClient.STATUS_CONNECTION_ERROR:
promise.reject("Connection Error")
return
HTTPClient.STATUS_BODY:
if not response._has_response:
response._has_response = true
response._status_code = client.get_response_code()
response._content_length = client.get_response_body_length()
response._headers = client.get_response_headers_as_dictionary()
var chunk = client.read_response_body_chunk()
if chunk.is_empty():
continue
response._response_chunks.append(chunk)
HTTPClient.STATUS_CONNECTED:
promise.resolve(response)
return
pass
func _resolve(promise: Promise, request: RequestInfo, response: Response):
if response.status_code() == 301:
var new_request = RequestInfo.new(request.method, request.path)
new_request.headers = request.headers
new_request.body = request.body
var path: String = response.headers()["Location"]
if path.begins_with("http://") or path.begins_with("https://"):
var idx = path.find('/', 8)
var host
if idx >= 0:
host = path.substr(0, idx)
path = path.substr(idx)
else:
host = path
path = '/'
var url = parseUrl(host)
if url.host != self.host:
printerr("Cannot connect to new host ", host, self.host)
promise.resolve(response)
return
else:
new_request.path = path
promise.resolve(fetch(new_request))
pass
else:
promise.resolve(response)
func fetch(request = null) -> Promise:
if request == null:
request = RequestInfo.new()
elif request is String:
var path = request
request = RequestInfo.new()
request.path = path
return RunPromise.new(_request, [request])

View File

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

View File

@@ -0,0 +1,62 @@
extends RefCounted
const ps = preload("res://addons/godot_colyseus/lib/promises.gd")
class Callback:
var fn: Callable
var args
var once = false
func emit(arg: Array):
var parmas = []
parmas.append_array(arg)
parmas.append_array(args)
fn.callv(parmas)
var cbs = []
func on(fn: Callable, args: Array = []):
var cb = Callback.new()
cb.fn = fn
cb.args = args
cbs.append(cb)
func off(fn: Callable):
var willremove = []
for cb in cbs:
if cb.fn == fn:
willremove.append(cb)
for cb in willremove:
cbs.erase(cb)
func once(fn: Callable, args: Array = []):
var cb = Callback.new()
cb.fn = fn
cb.args = args
cb.once = true
cbs.append(cb)
func wait() -> ps.Promise:
var promise = ps.Promise.new()
once(Callable(self, "_on_event"), [promise])
return promise
func _on_event(data, promise: ps.Promise):
promise.resolve(data)
func emit(argv: Array = []):
var willremove = []
var size = cbs.size()
for i in range(size):
var cb = cbs[i]
if !is_instance_valid(cb):
cbs.remove_at(i)
i -= 1
size -= 1
continue
cb.emit(argv)
if cb.once:
willremove.append(cb)
for cb in willremove:
cbs.erase(cb)

View File

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

View File

@@ -0,0 +1,487 @@
# Copyright (C) 2019 Tintin Ho
#
# 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.
#
#
# godot-msgpack
#
# This is a MessagePack serializer written in pure GDSciprt. To install this
# library in your project simply and copy and paste this file inside your
# project (e.g. res://msgpack.gd).
#
#
# class Msgpack
# static Dictionary encode(Variant value)
# Convert a value (number, string, array and dictionary) into their
# counterparts in messagepack. Returns dictionary with three fields:
# `result` which is the packed data (a PackedByteArray); `error` which is the
# error code; and `error_string` which is a human readable error message
#
# static Dictionary decode(PackedByteArray bytes)
# Convert a packed data (a PackedByteArray) into a value, the reverse of the
# encode function. The return value is similar to the one in the encode
# method
static func encode(value, buffer: StreamPeerBuffer):
var ctx = {error = OK, error_string = ""}
buffer.big_endian = true
_encode(buffer, value, ctx)
if ctx.error == OK:
return {
result = buffer.data_array,
error = OK,
error_string = "",
}
else:
return {
result = PackedByteArray(),
error = ctx.error,
error_string = ctx.error_string,
}
static func decode(buffer: StreamPeerBuffer):
buffer.big_endian = true
var ctx = {error = OK, error_string = ""}
var value = _decode(buffer, ctx)
if ctx.error == OK:
if buffer.get_position() == buffer.get_size():
return {result = value, error = OK, error_string = ""}
else:
var msg = "excess buffer %s bytes" % [buffer.get_size() - buffer.get_position()]
return {result = null, error = FAILED, error_string = msg}
else:
return {result = null, error = ctx.error, error_string = ctx.error_string}
static func _encode(buf, value, ctx):
match typeof(value):
TYPE_NIL:
buf.put_u8(0xc0)
TYPE_BOOL:
if value:
buf.put_u8(0xc3)
else:
buf.put_u8(0xc2)
TYPE_INT:
if -(1 << 5) <= value and value <= (1 << 7) - 1:
# fixnum (positive and negative)
buf.put_8(value)
elif -(1 << 7) <= value and value <= (1 << 7):
buf.put_u8(0xd0)
buf.put_8(value)
elif -(1 << 15) <= value and value <= (1 << 15):
buf.put_u8(0xd1)
buf.put_16(value)
elif -(1 << 31) <= value and value <= (1 << 31):
buf.put_u8(0xd2)
buf.put_32(value)
else:
buf.put_u8(0xd3)
buf.put_64(value)
TYPE_FLOAT:
buf.put_u8(0xcb)
buf.put_double(value)
TYPE_STRING:
var bytes = value.to_utf8_buffer()
var size = bytes.size()
if size <= (1 << 5) - 1:
# type fixstr [101XXXXX]
buf.put_u8(0xa0 | size)
elif size <= (1 << 8) - 1:
# type str 8
buf.put_u8(0xd9)
buf.put_u8(size)
elif size <= (1 << 16) - 1:
# type str 16
buf.put_u8(0xda)
buf.put_u16(size)
elif size <= (1 << 32) - 1:
# type str 32
buf.put_u8(0xdb)
buf.put_u32(size)
else:
assert(false)
buf.put_data(bytes)
TYPE_PACKED_BYTE_ARRAY:
var size = value.size()
if size <= (1 << 8) - 1:
buf.put_u8(0xc4)
buf.put_u8(size)
elif size <= (1 << 16) - 1:
buf.put_u8(0xc5)
buf.put_u16(size)
elif size <= (1 << 32) - 1:
buf.put_u8(0xc6)
buf.put_u32(size)
else:
assert(false)
buf.put_data(value)
TYPE_ARRAY:
var size = value.size()
if size <= 15:
# type fixarray [1001XXXX]
buf.put_u8(0x90 | size)
elif size <= (1 << 16) - 1:
# type array 16
buf.put_u8(0xdc)
buf.put_u16(size)
elif size <= (1 << 32) - 1:
# type array 32
buf.put_u8(0xdd)
buf.put_u32(size)
else:
assert(false)
for obj in value:
_encode(buf, obj, ctx)
if ctx.error != OK:
return
TYPE_DICTIONARY:
var size = value.size()
if size <= 15:
# type fixmap [1000XXXX]
buf.put_u8(0x80 | size)
elif size <= (1 << 16) - 1:
# type map 16
buf.put_u8(0xde)
buf.put_u16(size)
elif size <= (1 << 32) - 1:
# type map 32
buf.put_u8(0xdf)
buf.put_u32(size)
else:
assert(false)
for key in value:
_encode(buf, key, ctx)
if ctx.error != OK:
return
_encode(buf, value[key], ctx)
if ctx.error != OK:
return
_:
ctx.error = FAILED
ctx.error_string = "unsupported data type %s" % [typeof(value)]
static func _decode(buffer, ctx):
if buffer.get_position() == buffer.get_size():
ctx.error = FAILED
ctx.error_string = "unexpected end of input"
return null
var head = buffer.get_u8()
if head == 0xc0:
return null
elif head == 0xc2:
return false
elif head == 0xc3:
return true
# Integers
elif head & 0x80 == 0:
# positive fixnum
return head
elif (~head) & 0xe0 == 0:
# negative fixnum
return head - 256
elif head == 0xcc:
# uint 8
if buffer.get_size() - buffer.get_position() < 1:
ctx.error = FAILED
ctx.error_string = "not enough buffer for uint8"
return null
return buffer.get_u8()
elif head == 0xcd:
# uint 16
if buffer.get_size() - buffer.get_position() < 2:
ctx.error = FAILED
ctx.error_string = "not enough buffer for uint16"
return null
return buffer.get_u16()
elif head == 0xce:
# uint 32
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for uint32"
return null
return buffer.get_u32()
elif head == 0xcf:
# uint 64
if buffer.get_size() - buffer.get_position() < 8:
ctx.error = FAILED
ctx.error_string = "not enough buffer for uint64"
return null
return buffer.get_u64()
elif head == 0xd0:
# int 8
if buffer.get_size() - buffer.get_position() < 1:
ctx.error = FAILED
ctx.error_string = "not enogh buffer for int8"
return null
return buffer.get_8()
elif head == 0xd1:
# int 16
if buffer.get_size() - buffer.get_position() < 2:
ctx.error = FAILED
ctx.error_string = "not enogh buffer for int16"
return null
return buffer.get_16()
elif head == 0xd2:
# int 32
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for int32"
return null
return buffer.get_32()
elif head == 0xd3:
# int 64
if buffer.get_size() - buffer.get_position() < 8:
ctx.error = FAILED
ctx.error_string = "not enough buffer for int64"
return null
return buffer.get_64()
# Float
elif head == 0xca:
# float32
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for float32"
return null
return buffer.get_float()
elif head == 0xcb:
# float64
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for float64"
return null
return buffer.get_double()
# String
elif (~head) & 0xa0 == 0:
var size = head & 0x1f
if buffer.get_size() - buffer.get_position() < size:
ctx.error = FAILED
ctx.error_string = "not enough buffer for fixstr required %s bytes" % [size]
return null
return buffer.get_utf8_string(size)
elif head == 0xd9:
if buffer.get_size() - buffer.get_position() < 1:
ctx.error = FAILED
ctx.error_string = "not enough buffer for str8 size"
return null
var size = buffer.get_u8()
if buffer.get_size() - buffer.get_position() < size:
ctx.error = FAILED
ctx.error_string = "not enough buffer for str8 data required %s bytes" % [size]
return null
return buffer.get_utf8_string(size)
elif head == 0xda:
if buffer.get_size() - buffer.get_position() < 2:
ctx.error = FAILED
ctx.error_string = "not enough buffer for str16 size"
return null
var size = buffer.get_u16()
if buffer.get_size() - buffer.get_position() < size:
ctx.error = FAILED
ctx.error_string = "not enough buffer for str16 data required %s bytes" % [size]
return null
return buffer.get_utf8_string(size)
elif head == 0xdb:
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for str32 size"
return null
var size = buffer.get_u32()
if buffer.get_size() - buffer.get_position() < size:
ctx.error = FAILED
ctx.error_string = "not enough buffer for str32 data required %s bytes" % [size]
return null
return buffer.get_utf8_string(size)
# Binary
elif head == 0xc4:
if buffer.get_size() - buffer.get_position() < 1:
ctx.error = FAILED
ctx.error_string = "not enough buffer for bin8 size"
return null
var size = buffer.get_u8()
if buffer.get_size() - buffer.get_position() < size:
ctx.error = FAILED
ctx.error_string = "not enough buffer for bin8 data required %s bytes" % [size]
return null
var res = buffer.get_data(size)
assert(res[0] == OK)
return res[1]
elif head == 0xc5:
if buffer.get_size() - buffer.get_position() < 2:
ctx.error = FAILED
ctx.error_string = "not enough buffer for bin16 size"
return null
var size = buffer.get_u16()
if buffer.get_size() - buffer.get_position() < size:
ctx.error = FAILED
ctx.error_string = "not enough buffer for bin16 data required %s bytes" % [size]
return null
var res = buffer.get_data(size)
assert(res[0] == OK)
return res[1]
elif head == 0xc6:
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for bin32 size"
return null
var size = buffer.get_u32()
if buffer.get_size() - buffer.get_position() < size:
ctx.error = FAILED
ctx.error_string = "not enough buffer for bin32 data required %s bytes" % [size]
return null
var res = buffer.get_data(size)
assert(res[0] == OK)
return res[1]
# Array
elif head & 0xf0 == 0x90:
var size = head & 0x0f
var res = []
for i in range(size):
res.append(_decode(buffer, ctx))
if ctx.error != OK:
return null
return res
elif head == 0xdc:
if buffer.get_size() - buffer.get_position() < 2:
ctx.error = FAILED
ctx.error_string = "not enough buffer for array16 size"
return null
var size = buffer.get_u16()
var res = []
for i in range(size):
res.append(_decode(buffer, ctx))
if ctx.error != OK:
return null
return res
elif head == 0xdd:
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for array32 size"
return null
var size = buffer.get_u32()
var res = []
for i in range(size):
res.append(_decode(buffer, ctx))
if ctx.error != OK:
return null
return res
# Map
elif head & 0xf0 == 0x80:
var size = head & 0x0f
var res = {}
for i in range(size):
var k = _decode(buffer, ctx)
if ctx.error != OK:
return null
var v = _decode(buffer, ctx)
if ctx.error != OK:
return null
res[k] = v
return res
elif head == 0xde:
if buffer.get_size() - buffer.get_position() < 2:
ctx.error = FAILED
ctx.error_string = "not enough buffer for map16 size"
return null
var size = buffer.get_u16()
var res = {}
for i in range(size):
var k = _decode(buffer, ctx)
if ctx.error != OK:
return null
var v = _decode(buffer, ctx)
if ctx.error != OK:
return null
res[k] = v
return res
elif head == 0xdf:
if buffer.get_size() - buffer.get_position() < 4:
ctx.error = FAILED
ctx.error_string = "not enough buffer for map32 size"
return null
var size = buffer.get_u32()
var res = {}
for i in range(size):
var k = _decode(buffer, ctx)
if ctx.error != OK:
return null
var v = _decode(buffer, ctx)
if ctx.error != OK:
return null
res[k] = v
return res
else:
ctx.error = FAILED
ctx.error_string = "invalid byte tag %02X at pos %s" % [head, buffer.get_position()]
return null

View File

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

View File

@@ -0,0 +1,9 @@
extends Node
const ADD = 128
const REPLACE = 0
const DELETE = 64
const DELETE_AND_ADD = 192
const TOUCH = 1
const CLEAR = 10
const SWITCH_TO_STRUCTURE = 255

View File

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

View File

@@ -0,0 +1,107 @@
extends RefCounted
class Promise:
enum State {
Waiting,
Success,
Failed
}
var result
signal completed
var _state: State = State.Waiting
func get_state() -> State:
return _state
func resolve(res = null):
if res is Promise:
await res.completed
result = res.result
_state = res.get_state()
emit_signal("completed")
else:
result = res
_state = State.Success
emit_signal("completed")
func reject(error = null):
result = error
_state = State.Failed
emit_signal("completed")
var data:get = get_data
func get_data():
if _state == State.Success:
return result
return null
var error:get = get_error
func get_error():
if _state == State.Failed:
return result
return null
var is_failed : bool :
get():
return _state == State.Failed
func wait():
if _state == State.Waiting:
await self.completed
return self
func _to_string():
match _state:
State.Waiting:
return "[Waiting]"
State.Success:
return str("[Success:",result,"]")
State.Failed:
return str("[Failed:",result,"]")
func _next(promise, callback: Callable, argv: Array):
await wait()
if _state == State.Success:
var arr = [get_data(), promise]
arr.append_array(argv)
var ret = await callback.callv(arr)
promise.resolve(ret)
func then(callback: Callable, argv: Array = []) -> Promise:
return RunPromise.new(Callable(self, "_next"), [callback, argv])
class FramePromise extends Promise:
var cb: Callable
var argv: Array
func _init(callback: Callable,argv: Array = []):
cb = callback
self.argv = argv
_run()
func _run():
var root = Engine.get_main_loop()
while true:
if root is SceneTree:
await root.process_frame
var arr = [self]
arr.append_array(argv)
cb.callv(arr)
if get_state() != State.Waiting:
break
class RunPromise extends Promise:
var cb: Callable
var argv: Array
func _init(callback: Callable,argv: Array = []):
cb = callback
self.argv = argv
_run()
func _run():
var arr = [self]
arr.append_array(argv)
await cb.callv(arr)

View File

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

View File

@@ -0,0 +1,198 @@
extends RefCounted
const FrameRunner = preload("res://addons/godot_colyseus/lib/frame_runner.gd")
const EventListener = preload("res://addons/godot_colyseus/lib/listener.gd")
const ser = preload("res://addons/godot_colyseus/lib/serializer.gd")
const Decoder = preload("res://addons/godot_colyseus/lib/decoder.gd")
const Encoder = preload("res://addons/godot_colyseus/lib/encoder.gd")
const MsgPack = preload("res://addons/godot_colyseus/lib/msgpack.gd")
const Schema = preload("./schema.gd")
const CODE_HANDSHAKE = 9
const CODE_JOIN_ROOM = 10
const CODE_ERROR = 11
const CODE_LEAVE_ROOM = 12
const CODE_ROOM_DATA = 13
const CODE_ROOM_STATE = 14
const CODE_ROOM_STATE_PATCH = 15
const CODE_ROOM_DATA_SCHEMA = 16
const ERROR_MATCHMAKE_NO_HANDLER = 4210
const ERROR_MATCHMAKE_INVALID_CRITERIA = 4211
const ERROR_MATCHMAKE_INVALID_ROOM_ID = 4212
const ERROR_MATCHMAKE_UNHANDLED = 4213
const ERROR_MATCHMAKE_EXPIRED = 4214
const ERROR_AUTH_FAILED = 4215
const ERROR_APPLICATION_ERROR = 4216
var room_name: String
var room_id: String
var session_id: String
var serializer: ser.Serializer
var ws: WebSocketPeer
var frame_runner: FrameRunner
var reconnection_token: String
var schema_type: GDScript
var _has_joined = false
func has_joined() -> bool:
return _has_joined
# [code: int, message: String]
var on_error: EventListener = EventListener.new()
# []
var on_leave: EventListener = EventListener.new()
# []
var on_join: EventListener = EventListener.new()
# [state: Schema]
var on_state_change: EventListener = EventListener.new()
# [data]
var _messages = {}
func on_message(event: String, new_listener: bool = true) -> EventListener:
var listener
if not _messages.has(event) or new_listener:
listener = EventListener.new()
_messages[event] = listener
else:
listener = _messages[event]
return listener
func _init(room_name: String,schema_type: GDScript):
self.room_name = room_name
self.schema_type = schema_type
ws = WebSocketPeer.new()
#ws.connect("connection_established",Callable(self,"_connection_established"))
#ws.connect("connection_error",Callable(self,"_connection_error"))
#ws.connect("connection_closed",Callable(self,"_connection_closed"))
#ws.connect("data_received",Callable(self,"_on_data"))
frame_runner = FrameRunner.new(_on_frame)
func _connection_established(protocol):
pass
func _connection_error():
frame_runner.stop()
func _connection_closed(was_clean: bool):
frame_runner.stop()
func _on_data():
var data = ws.get_packet()
var reader = StreamPeerBuffer.new()
reader.data_array = data
var decoder = Decoder.new(reader)
var code = reader.get_u8()
match code:
CODE_JOIN_ROOM:
var token = reader.get_string(reader.get_u8())
var serializer_id = reader.get_string(reader.get_u8())
if serializer == null:
serializer = ser.getSerializer(serializer_id, schema_type)
if decoder.has_more():
if serializer:
serializer.handshake(decoder)
else:
on_error.emit([1, "Can not find serializer"])
return
self.reconnection_token = str(room_id, ":", token)
_has_joined = true
on_join.emit()
send_raw([CODE_JOIN_ROOM])
CODE_ERROR:
var message = decoder.read_utf8()
on_error.emit([0, message])
CODE_LEAVE_ROOM:
leave()
CODE_ROOM_DATA:
var type
if decoder.is_number():
type = str('i', decoder.number())
else:
type = decoder.read_utf8()
var listener = on_message(type, false)
if listener != null:
var ret = decoder.unpack()
if ret == null:
ret = {}
listener.emit([ret])
CODE_ROOM_STATE:
serializer.set_state(decoder)
on_state_change.emit([serializer.get_state()])
CODE_ROOM_STATE_PATCH:
serializer.patch(decoder)
on_state_change.emit([serializer.get_state()])
CODE_ROOM_DATA_SCHEMA:
print("Receive message CODE_ROOM_DATA_SCHEMA")
func connect_remote(url: String):
var _url = url
if url.begins_with("http:"):
_url = url.replace("http:", "ws:")
elif url.begins_with("https:"):
_url = url.replace("https:", "wss:")
#_url = _url.replace("/colyseus", "")
ws.connect_to_url(_url)
frame_runner.start()
func _on_frame():
ws.poll()
var state = ws.get_ready_state()
match state:
WebSocketPeer.STATE_OPEN:
while ws.get_available_packet_count() > 0:
_on_data()
WebSocketPeer.STATE_CLOSED:
var code = ws.get_close_code()
var reason = ws.get_close_reason()
_connection_closed(true)
if _has_joined:
leave()
func send_raw(bytes: PackedByteArray):
ws.send(bytes)
func send(type: String, message = null):
var buffer = StreamPeerBuffer.new()
buffer.put_u8(CODE_ROOM_DATA)
var encoder = Encoder.new(buffer)
if typeof(type) == TYPE_STRING:
encoder.string(type)
else:
encoder.number(type)
if message != null:
var result = MsgPack.encode(message, buffer)
assert(result.error == OK)
send_raw(buffer.data_array)
func leave(consented = true):
_has_joined = false
if not room_id.is_empty():
if consented:
send_raw([CODE_LEAVE_ROOM])
else:
ws.disconnect_from_host()
on_leave.emit()
var state : Schema : get = get_state
func get_state() -> Schema:
return serializer.get_state()

View File

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

View File

@@ -0,0 +1,18 @@
extends RefCounted
var clients: int
var created_at: String
var max_clients: int
var name: String
var process_id: String
var room_id: String
func _init(dic):
clients = dic.get('clients')
created_at = dic.get('createdAt')
var num = dic.get('maxClients')
if num != null:
max_clients = num
name = dic.get('name')
process_id = dic.get('processId')
room_id = dic.get('roomId')

View File

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

View File

@@ -0,0 +1,308 @@
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

View File

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

View File

@@ -0,0 +1,54 @@
extends RefCounted
const Types = preload("res://addons/godot_colyseus/lib/types.gd")
class Ref:
var value
var type_info
func _init(value,type_info):
self.value = value
self.type_info = type_info
var id
var parent
var parent_index: int
var parent_key
func clear(decoding: bool = false):
assert(false)
func meta_get(index):
assert(false)
func meta_get_key(index) -> String:
assert(false)
return ""
func meta_get_subtype(index):
assert(false)
func meta_set(index, key, value):
assert(false)
return null
func meta_remove(index):
assert(false)
func set_parent(np, pindex):
if parent == np and parent_index == pindex:
return
if parent != null:
parent.meta_remove(parent_index)
parent = np
parent_index = pindex
parent_key = parent.meta_get_key(parent_index)
func trigger(event: String, argv: Array = [], path: PackedStringArray = PackedStringArray(), target: Object = self):
if parent == null:
return
path.append(parent_key)
parent.trigger(event, argv, path, target)
func to_object():
return "<null>"

View File

@@ -0,0 +1 @@
uid://7hqsu5vttoy2

View File

@@ -0,0 +1,113 @@
extends RefCounted
const Schema = preload("res://addons/godot_colyseus/lib/schema.gd")
const Decoder = preload("res://addons/godot_colyseus/lib/decoder.gd")
const Types = preload("res://addons/godot_colyseus/lib/types.gd")
class Serializer:
func set_state(decoder):
pass
func get_state():
pass
func patch(decoder):
pass
func teardown():
pass
func handshake(decoder):
pass
class NoneSerializer extends Serializer:
pass
class ReflectionField extends Schema:
static func define_fields():
return [
Schema.Field.new("name", Schema.Types.STRING),
Schema.Field.new("type", Schema.Types.STRING),
Schema.Field.new("referenced_type", Schema.Types.NUMBER),
]
func test(field, reflection: Reflection) -> bool:
if self.type != field.current_type.to_string() or self.name != field.name:
var str1 = str(field.name, '-', self.name)
var str2 = str(field.current_type, '-', self.type)
printerr("Field not match ", str1, " : ", str2)
return false
if self.type == Schema.Types.REF:
var type = reflection.types.at(self.referenced_type)
return type.test(field.schema_type, reflection)
return true
class ReflectionType extends Schema:
static func define_fields():
return [
Schema.Field.new("id", Schema.Types.NUMBER),
Schema.Field.new("extendsId", Schema.Types.NUMBER),
Schema.Field.new("fields", Schema.Types.ARRAY, ReflectionField),
]
func test(schema_type, reflection: Reflection) -> bool:
if not schema_type is GDScript:
printerr("Type schema_type not match ", self.id)
return false
var fields = schema_type.define_fields()
var length = fields.size()
if length != self.fields.size():
printerr("Type fields count not match ", self.id)
return false
for i in range(length):
var field = self.fields.at(i)
if not field.test(fields[i], reflection):
return false
return true
class Reflection extends Schema:
static func define_fields():
return [
Schema.Field.new("types", Schema.Types.ARRAY, ReflectionType),
Schema.Field.new("root_type", Schema.Types.NUMBER),
]
func test(schema_type: GDScript) -> bool:
return self.types.at(self.root_type).test(schema_type, self)
class SchemaSerializer extends Serializer:
var state
var schema_type: GDScript
func _init(schema_type):
self.schema_type = schema_type
self.state = schema_type.new()
func handshake(decoder):
var reflection = Reflection.new()
reflection.decode(decoder)
assert(reflection.test(schema_type),"Can not detect schema type")
func set_state(decoder):
state.decode(decoder)
func get_state():
return state
func patch(decoder):
state.decode(decoder)
static func getSerializer(id: String, schema_type: GDScript = null) -> Serializer:
match id:
"schema":
return SchemaSerializer.new(schema_type)
"none":
return NoneSerializer.new()
return null

View File

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

View File

@@ -0,0 +1,97 @@
extends Object
const Decoder = preload("res://addons/godot_colyseus/lib/decoder.gd")
const types = preload("res://addons/godot_colyseus/lib/types.gd")
const collections = preload("res://addons/godot_colyseus/lib/collections.gd")
var type: String
var sub_type
func _init(type: String,sub_type = null):
self.type = type
self.sub_type = sub_type
func is_schema_type():
return type == types.REF or type == types.MAP or type == types.ARRAY or type == types.COLLECTION or type == types.SET
func _to_string():
var ret = type
if sub_type and sub_type.type != types.REF:
ret = str(ret, ':', sub_type.type)
return ret
func create():
match type:
types.REF:
return sub_type.new()
types.MAP:
var obj = collections.MapSchema.new()
obj.sub_type = sub_type
return obj
types.ARRAY:
var obj = collections.ArraySchema.new()
obj.sub_type = sub_type
return obj
types.SET:
var obj = collections.SetSchema.new()
obj.sub_type = sub_type
return obj
types.COLLECTION:
var obj = collections.CollectionSchema.new()
obj.sub_type = sub_type
return obj
func decode(decoder: Decoder):
match type:
types.REF:
var obj = sub_type.new()
obj.id = decoder.number()
return obj
types.MAP:
var obj = collections.MapSchema.new()
obj.id = decoder.number()
obj.sub_type = sub_type
return obj
types.ARRAY:
var obj = collections.ArraySchema.new()
obj.id = decoder.number()
obj.sub_type = sub_type
return obj
types.SET:
var obj = collections.SetSchema.new()
obj.id = decoder.number()
obj.sub_type = sub_type
return obj
types.COLLECTION:
var obj = collections.CollectionSchema.new()
obj.id = decoder.number()
obj.sub_type = sub_type
return obj
types.STRING:
return decoder.read_utf8()
types.NUMBER:
return decoder.number()
types.BOOLEAN:
return decoder.reader.get_u8() > 0
types.INT8:
return decoder.reader.get_8()
types.UINT8:
return decoder.reader.get_u8()
types.INT16:
return decoder.reader.get_16()
types.UINT16:
return decoder.reader.get_u16()
types.INT32:
return decoder.reader.get_32()
types.UINT32:
return decoder.reader.get_u32()
types.INT64:
return decoder.reader.get_64()
types.UINT64:
return decoder.reader.get_u64()
types.FLOAT32:
return decoder.reader.get_float()
types.FLOAT64:
return decoder.reader.get_double()
_:
assert(true) #,str("Unkown support type:", type))

View File

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

View File

@@ -0,0 +1,42 @@
extends Object
const REF = "ref"
const MAP = "map"
const ARRAY = "array"
const SET = "set"
const COLLECTION = "collection"
const STRING = "string"
const NUMBER = "number"
const BOOLEAN = "boolean"
const INT8 = "int8"
const UINT8 = "uint8"
const INT16 = "int16"
const UINT16 = "uint16"
const INT32 = "int32"
const UINT32 = "uint32"
const INT64 = "int64"
const UINT64 = "uint64"
const FLOAT32 = "float32"
const FLOAT64 = "float64"
static func to_gd_type(type: String) -> int:
match type:
REF:
return TYPE_OBJECT
MAP:
return TYPE_OBJECT
ARRAY:
return TYPE_OBJECT
SET:
return TYPE_OBJECT
COLLECTION:
return TYPE_OBJECT
STRING:
return TYPE_STRING
NUMBER, FLOAT32, FLOAT64:
return TYPE_FLOAT
BOOLEAN:
return TYPE_BOOL
INT8, UINT8, INT16, UINT16, INT32, UINT32, INT64, UINT64:
return TYPE_INT
return TYPE_NIL

View File

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

View File

@@ -0,0 +1,7 @@
[plugin]
name="godot-colyseus"
description=""
author="gsioteam"
version="0.1.0"
script="init.gd"