fix(network): prevent null multiplayer peer crashes offline/teardown and fix Nakama RPC session validation
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
extends Node
|
||||
|
||||
class Log:
|
||||
|
||||
enum {ERROR, INFO, DEBUG}
|
||||
|
||||
static func _s(lvl):
|
||||
if lvl == ERROR:
|
||||
return "ERROR"
|
||||
elif lvl == INFO:
|
||||
return "INFO"
|
||||
elif lvl == DEBUG:
|
||||
return "DEBUG"
|
||||
return "WTF"
|
||||
|
||||
static func __log(lvl, msg, data):
|
||||
print("======= %s: %s" % [_s(lvl), msg])
|
||||
if not data.is_empty():
|
||||
var json = JSON.new()
|
||||
print(json.stringify(data, " ", true))
|
||||
|
||||
static func error(msg, data={}):
|
||||
__log(ERROR, msg, data)
|
||||
|
||||
static func info(msg, data={}):
|
||||
__log(INFO, msg, data)
|
||||
|
||||
static func debug(msg, data={}):
|
||||
__log(DEBUG, msg, data)
|
||||
|
||||
const REAL_ASSERT = false
|
||||
|
||||
var _success = false
|
||||
var _start_time = 0
|
||||
var _quit = false
|
||||
var _disabled = false
|
||||
var __me = ""
|
||||
|
||||
# Override this to specify test initialization (called on _ready if not disabled)
|
||||
# You can run your whole test here, and call done() when finished
|
||||
func setup():
|
||||
pass
|
||||
|
||||
# Override this to do cleanup, make assertions at the end of the test (called on _exit_tree)
|
||||
# NOTE: You can still fail here if you like with fail() or by failing an assertion
|
||||
func teardown():
|
||||
pass
|
||||
|
||||
func _init():
|
||||
__me = get_script()
|
||||
while __me.get_base_script() != null:
|
||||
__me = __me.get_base_script()
|
||||
|
||||
func _ready():
|
||||
if _disabled:
|
||||
set_process(false)
|
||||
set_physics_process(false)
|
||||
set_process_input(false)
|
||||
set_process_unhandled_input(false)
|
||||
set_process_unhandled_key_input(false)
|
||||
done()
|
||||
Log.info("SKIP: %s" % __get_source(self))
|
||||
return
|
||||
_start_time = Time.get_ticks_usec()
|
||||
Log.info("RUNNING: %s" % __get_source(self))
|
||||
setup()
|
||||
|
||||
func _exit_tree():
|
||||
if _disabled:
|
||||
return
|
||||
|
||||
teardown()
|
||||
Log.info("%s: %s" % ["SUCCESS" if _success else "!!!!!!!!!!!!!!!!!!! FAILURE", __get_source(self)])
|
||||
|
||||
func __get_source(who):
|
||||
if who == null or who.get_script() == null:
|
||||
return "Unknown source: %s" % str(who)
|
||||
return who.get_script().resource_path
|
||||
|
||||
func __get_caller():
|
||||
for s in get_stack():
|
||||
if __me.resource_path != s.source:
|
||||
return s
|
||||
return null
|
||||
|
||||
func __get_assertion():
|
||||
var stack = get_stack()
|
||||
stack.reverse()
|
||||
for s in stack:
|
||||
if __me.resource_path == s.source:
|
||||
return s
|
||||
return null
|
||||
|
||||
func __assert(v1, v2):
|
||||
if not (v1 == v2):
|
||||
Log.error("Assert Failed", {"caller": __get_caller(), "assertion": __get_assertion(), "full_stack": get_stack()})
|
||||
print_tree_pretty()
|
||||
_quit = true
|
||||
if REAL_ASSERT:
|
||||
assert(v1 == v2)
|
||||
if v1 == v2:
|
||||
return false
|
||||
return true
|
||||
|
||||
func assert_time(max_time):
|
||||
__assert(max_time > float(Time.get_ticks_usec() - _start_time) / 1000.0 / 1000.0, true)
|
||||
|
||||
### Returns true if the assertion failed, so you can do:
|
||||
### if assert_cond(cond):
|
||||
### return # Assertion failed!
|
||||
func assert_cond(cond):
|
||||
return __assert(cond, true)
|
||||
|
||||
func assert_false(cond):
|
||||
return __assert(cond, false)
|
||||
|
||||
func assert_equal(v1, v2):
|
||||
return __assert(v1, v2)
|
||||
|
||||
func done():
|
||||
_success = true
|
||||
_quit = true
|
||||
|
||||
func fail():
|
||||
_success = false
|
||||
_quit = true
|
||||
__assert(false, true)
|
||||
|
||||
func disable():
|
||||
_disabled = true
|
||||
|
||||
func is_disabled():
|
||||
return _disabled
|
||||
@@ -0,0 +1,2 @@
|
||||
*
|
||||
!.gitignore
|
||||
@@ -0,0 +1,119 @@
|
||||
; 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
|
||||
|
||||
_global_script_classes=[{
|
||||
"base": "RefCounted",
|
||||
"class": &"NakamaAPI",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/api/NakamaAPI.gd"
|
||||
}, {
|
||||
"base": "RefCounted",
|
||||
"class": &"NakamaAsyncResult",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaAsyncResult.gd"
|
||||
}, {
|
||||
"base": "RefCounted",
|
||||
"class": &"NakamaClient",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/client/NakamaClient.gd"
|
||||
}, {
|
||||
"base": "RefCounted",
|
||||
"class": &"NakamaException",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaException.gd"
|
||||
}, {
|
||||
"base": "Node",
|
||||
"class": &"NakamaHTTPAdapter",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/client/NakamaHTTPAdapter.gd"
|
||||
}, {
|
||||
"base": "RefCounted",
|
||||
"class": &"NakamaLogger",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaLogger.gd"
|
||||
}, {
|
||||
"base": "RefCounted",
|
||||
"class": &"NakamaMultiplayerBridge",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaMultiplayerBridge.gd"
|
||||
}, {
|
||||
"base": "MultiplayerPeerExtension",
|
||||
"class": &"NakamaMultiplayerPeer",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaMultiplayerPeer.gd"
|
||||
}, {
|
||||
"base": "NakamaAsyncResult",
|
||||
"class": &"NakamaRTAPI",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/api/NakamaRTAPI.gd"
|
||||
}, {
|
||||
"base": "RefCounted",
|
||||
"class": &"NakamaRTMessage",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/api/NakamaRTMessage.gd"
|
||||
}, {
|
||||
"base": "RefCounted",
|
||||
"class": &"NakamaSerializer",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaSerializer.gd"
|
||||
}, {
|
||||
"base": "NakamaAsyncResult",
|
||||
"class": &"NakamaSession",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/api/NakamaSession.gd"
|
||||
}, {
|
||||
"base": "RefCounted",
|
||||
"class": &"NakamaSocket",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/socket/NakamaSocket.gd"
|
||||
}, {
|
||||
"base": "Node",
|
||||
"class": &"NakamaSocketAdapter",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/socket/NakamaSocketAdapter.gd"
|
||||
}, {
|
||||
"base": "RefCounted",
|
||||
"class": &"NakamaStorageObjectId",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/api/NakamaStorageObjectId.gd"
|
||||
}, {
|
||||
"base": "RefCounted",
|
||||
"class": &"NakamaWriteStorageObject",
|
||||
"language": &"GDScript",
|
||||
"path": "res://addons/com.heroiclabs.nakama/api/NakamaWriteStorageObject.gd"
|
||||
}]
|
||||
_global_script_class_icons={
|
||||
"NakamaAPI": "",
|
||||
"NakamaAsyncResult": "",
|
||||
"NakamaClient": "",
|
||||
"NakamaException": "",
|
||||
"NakamaHTTPAdapter": "",
|
||||
"NakamaLogger": "",
|
||||
"NakamaMultiplayerBridge": "",
|
||||
"NakamaMultiplayerPeer": "",
|
||||
"NakamaRTAPI": "",
|
||||
"NakamaRTMessage": "",
|
||||
"NakamaSerializer": "",
|
||||
"NakamaSession": "",
|
||||
"NakamaSocket": "",
|
||||
"NakamaSocketAdapter": "",
|
||||
"NakamaStorageObjectId": "",
|
||||
"NakamaWriteStorageObject": ""
|
||||
}
|
||||
|
||||
[application]
|
||||
|
||||
run/main_scene="res://tester.tscn"
|
||||
config/features=PackedStringArray("4.0")
|
||||
|
||||
[autoload]
|
||||
|
||||
Nakama="*res://addons/com.heroiclabs.nakama/Nakama.gd"
|
||||
Config="*res://utils/config.gd"
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
|
||||
END_STRING="======= TESTS END"
|
||||
PROJECT_PATH="test_suite/"
|
||||
GODOT_BIN="test_suite/bin/godot.elf"
|
||||
OUT=`$GODOT_BIN --headless --debug --path test_suite/ -s res://runner.gd`
|
||||
RUN=`echo $OUT | grep "$END_STRING"`
|
||||
RES=`echo $OUT | grep FAILURE`
|
||||
|
||||
echo "$OUT"
|
||||
|
||||
if [ -z "$RUN" ]; then
|
||||
echo "Run failed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$RES" ]; then
|
||||
echo "Tests passed!"
|
||||
exit 0
|
||||
fi
|
||||
echo "Tests failed!"
|
||||
exit 1
|
||||
@@ -0,0 +1,15 @@
|
||||
extends SceneTree
|
||||
|
||||
func _init():
|
||||
var me = ProjectSettings.localize_path(ProjectSettings.globalize_path(get_script().resource_path))
|
||||
var dir = ProjectSettings.localize_path(me.get_base_dir())
|
||||
|
||||
if dir != "res://":
|
||||
print("Must be run with the script dir as the res:// path")
|
||||
print("Current path: ", ProjectSettings.globalize_path(dir))
|
||||
print("RES:// path: ", ProjectSettings.globalize_path("res://"))
|
||||
quit()
|
||||
return
|
||||
var n = load("res://tester.gd").new()
|
||||
root.add_child(n)
|
||||
current_scene = n
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
{
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": 7350,
|
||||
"SCHEME": "http",
|
||||
"SERVER_KEY": "defaultkey"
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
extends Node
|
||||
|
||||
var current = null
|
||||
var tests = []
|
||||
var frame_time = 0.0
|
||||
var fixed_time = 0.0
|
||||
var dirs = ["tests"]
|
||||
|
||||
func _ready():
|
||||
while not dirs.is_empty():
|
||||
var d = dirs.pop_front()
|
||||
_expand(d, dirs)
|
||||
print("Running %d tests:\n%s" % [tests.size(), tests])
|
||||
|
||||
func _process(delta):
|
||||
frame_time += delta
|
||||
_check_end()
|
||||
|
||||
func _physics_process(delta):
|
||||
fixed_time += delta
|
||||
_check_end()
|
||||
|
||||
func _check_end():
|
||||
if current == null:
|
||||
if tests.size() == 0:
|
||||
_end_tests()
|
||||
return
|
||||
current = load(tests.pop_front()).new()
|
||||
add_child(current)
|
||||
elif current._quit:
|
||||
if not current._success:
|
||||
_end_tests()
|
||||
current.queue_free()
|
||||
current = null
|
||||
|
||||
func _end_tests():
|
||||
print("======= TESTS END")
|
||||
set_process(false)
|
||||
set_physics_process(false)
|
||||
await get_tree().create_timer(1.0).timeout
|
||||
get_tree().call_deferred("quit")
|
||||
|
||||
func _expand(p_name, r_dirs):
|
||||
var dir = DirAccess.open("res://")
|
||||
if dir.change_dir(p_name) != OK:
|
||||
print("Unable to chdir into: %s" % p_name)
|
||||
return
|
||||
dir.list_dir_begin()
|
||||
var f = dir.get_next()
|
||||
while f != "":
|
||||
if dir.current_is_dir():
|
||||
r_dirs.append("%s/%s" % [p_name, f])
|
||||
if f.ends_with("_test.gd"):
|
||||
tests.append("%s/%s" % [p_name, f])
|
||||
f = dir.get_next()
|
||||
dir.list_dir_end()
|
||||
@@ -0,0 +1,12 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://bglgahpqv1ojs"]
|
||||
|
||||
[ext_resource type="Script" path="res://tester.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")
|
||||
@@ -0,0 +1,32 @@
|
||||
extends "res://base_test.gd"
|
||||
|
||||
func setup():
|
||||
var username = str(randi_range(1000, 100000))
|
||||
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||
# POST
|
||||
var session = await client.authenticate_custom_async("MyIdentifier")
|
||||
if assert_cond(session.is_valid()) or assert_cond(!session.is_expired()):
|
||||
return
|
||||
# PUT
|
||||
var update = await client.update_account_async(session, username)
|
||||
if assert_false(update.is_exception()):
|
||||
return
|
||||
# GET
|
||||
var account : NakamaAPI.ApiAccount = await client.get_account_async(session)
|
||||
if assert_false(account.is_exception()):
|
||||
return
|
||||
if assert_cond(account.user.username == username):
|
||||
return
|
||||
# POST - DELETE
|
||||
var group : NakamaAPI.ApiGroup = await client.create_group_async(session, "MyGroupName3")
|
||||
if assert_false(group.is_exception()):
|
||||
return
|
||||
var delete = await client.delete_group_async(session, group.id)
|
||||
if assert_false(delete.is_exception()):
|
||||
return
|
||||
# All good
|
||||
done()
|
||||
return
|
||||
|
||||
func _process(_delta):
|
||||
assert_time(3)
|
||||
@@ -0,0 +1,79 @@
|
||||
extends "res://base_test.gd"
|
||||
|
||||
var content = {"My": "message"}
|
||||
var match_string_props = {"region": "europe"}
|
||||
var match_numeric_props = {"rank": 8}
|
||||
var got_msg = false
|
||||
var got_match = false
|
||||
var socket1 = null
|
||||
var socket2 = null
|
||||
|
||||
func setup():
|
||||
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||
|
||||
var session1 = await client.authenticate_custom_async("MyIdentifier")
|
||||
if assert_cond(session1.is_valid()):
|
||||
return
|
||||
|
||||
var session2 = await client.authenticate_custom_async("MyIdentifier2")
|
||||
if assert_cond(session1.is_valid()):
|
||||
return
|
||||
|
||||
socket1 = Nakama.create_socket_from(client)
|
||||
socket1.received_channel_message.connect(self._on_socket1_message)
|
||||
socket1.received_matchmaker_matched.connect(self._on_socket1_matchmaker_matched)
|
||||
var done = await socket1.connect_async(session1)
|
||||
# Check that connection succeded
|
||||
if assert_false(done.is_exception()):
|
||||
return
|
||||
|
||||
socket2 = Nakama.create_socket_from(client)
|
||||
done = await socket2.connect_async(session2)
|
||||
# Check that connection succeded
|
||||
if assert_false(done.is_exception()):
|
||||
return
|
||||
|
||||
# Join room
|
||||
var room1 = await socket1.join_chat_async("MyRoom", NakamaSocket.ChannelType.Room)
|
||||
if assert_false(room1.is_exception()):
|
||||
return
|
||||
var room2 = await socket2.join_chat_async("MyRoom", NakamaSocket.ChannelType.Room)
|
||||
if assert_false(room2.is_exception()):
|
||||
return
|
||||
|
||||
# Socket 2 send message to socket 1
|
||||
var msg_ack = await socket2.write_chat_message_async(room2.id, content)
|
||||
if assert_false(msg_ack.is_exception()):
|
||||
return
|
||||
|
||||
var ticket1 = await socket1.add_matchmaker_async("+properties.region:europe +properties.rank:>=7 +properties.rank:<=9", 2, 8, match_string_props, match_numeric_props)
|
||||
if assert_false(ticket1.is_exception()):
|
||||
return
|
||||
var ticket2 = await socket2.add_matchmaker_async("+properties.region:europe +properties.rank:>=7 +properties.rank:<=9", 2, 8, match_string_props, match_numeric_props)
|
||||
if assert_false(ticket2.is_exception()):
|
||||
return
|
||||
|
||||
func _on_socket1_message(msg):
|
||||
if assert_equal(msg.content, JSON.stringify(content)):
|
||||
return
|
||||
got_msg = true
|
||||
check_end()
|
||||
|
||||
func _on_socket1_matchmaker_matched(p_matchmaker_matched):
|
||||
if assert_equal(JSON.stringify(p_matchmaker_matched.users[0].string_properties), JSON.stringify(match_string_props)):
|
||||
return
|
||||
if assert_equal(JSON.stringify(p_matchmaker_matched.users[0].numeric_properties), JSON.stringify(match_numeric_props)):
|
||||
return
|
||||
if assert_equal(JSON.stringify(p_matchmaker_matched.users[1].string_properties), JSON.stringify(match_string_props)):
|
||||
return
|
||||
if assert_equal(JSON.stringify(p_matchmaker_matched.users[1].numeric_properties), JSON.stringify(match_numeric_props)):
|
||||
return
|
||||
got_match = true
|
||||
check_end()
|
||||
|
||||
func _process(_delta):
|
||||
assert_time(60)
|
||||
|
||||
func check_end():
|
||||
if got_match and got_msg:
|
||||
done()
|
||||
@@ -0,0 +1,98 @@
|
||||
extends "res://base_test.gd"
|
||||
|
||||
var content = {"My": "message"}
|
||||
var match_props = {"region": "europe"}
|
||||
var got_msg = false
|
||||
var got_match = false
|
||||
var socket1 : NakamaSocket = null
|
||||
var socket2 : NakamaSocket = null
|
||||
|
||||
func setup():
|
||||
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||
|
||||
var session1 = await client.authenticate_custom_async("MyIdentifier")
|
||||
if assert_cond(session1.is_valid()):
|
||||
return
|
||||
|
||||
var session2 = await client.authenticate_custom_async("MyIdentifier2")
|
||||
if assert_cond(session2.is_valid()):
|
||||
return
|
||||
|
||||
socket1 = Nakama.create_socket_from(client)
|
||||
socket1.received_party_close.connect(self._on_party_close)
|
||||
socket1.received_party_data.connect(self._on_party_data)
|
||||
socket1.received_party_join_request.connect(self._on_party_join_request)
|
||||
|
||||
var conn = await socket1.connect_async(session1)
|
||||
# Check that connection succeded
|
||||
if assert_false(conn.is_exception()):
|
||||
return
|
||||
|
||||
var party = await socket1.create_party_async(false, 2)
|
||||
if assert_false(party.is_exception()):
|
||||
return
|
||||
#done()
|
||||
|
||||
socket2 = Nakama.create_socket_from(client)
|
||||
socket2.received_party.connect(self._on_party)
|
||||
socket2.received_party_close.connect(self._on_party_close)
|
||||
socket2.received_party_join_request.connect(self._on_party_join_request)
|
||||
socket2.received_party_leader.connect(self._on_party_leader)
|
||||
socket2.received_party_presence.connect(self._on_party_presence)
|
||||
|
||||
var conn2 = await socket2.connect_async(session2)
|
||||
# Check that connection succeded
|
||||
if assert_false(conn2.is_exception()):
|
||||
return
|
||||
|
||||
var join = await socket2.join_party_async(party.party_id)
|
||||
if assert_false(join.is_exception()):
|
||||
return
|
||||
|
||||
func _on_party_join_request(party_join_request : NakamaRTAPI.PartyJoinRequest):
|
||||
prints("_on_party_join_request", party_join_request)
|
||||
var requests : NakamaRTAPI.PartyJoinRequest = await socket1.list_party_join_requests_async(party_join_request.party_id)
|
||||
if assert_false(requests.is_exception()):
|
||||
return
|
||||
if assert_cond(requests.presences.size() == 1):
|
||||
return
|
||||
|
||||
await socket1.accept_party_member_async(party_join_request.party_id, party_join_request.presences[0])
|
||||
await socket1.promote_party_member(party_join_request.party_id, party_join_request.presences[0])
|
||||
|
||||
func _on_party(party):
|
||||
prints("_on_party", party)
|
||||
|
||||
func _on_party_close(party_close):
|
||||
prints("_on_party_close", party_close)
|
||||
|
||||
func _on_party_data(data : NakamaRTAPI.PartyData):
|
||||
prints("_on_party_data", data)
|
||||
var left = await socket1.leave_party_async(data.party_id)
|
||||
if assert_false(left.is_exception()):
|
||||
return
|
||||
|
||||
func _on_party_leader(party_leader : NakamaRTAPI.PartyLeader):
|
||||
prints("_on_party_leader", party_leader)
|
||||
var ticket = await socket2.add_matchmaker_party_async(party_leader.party_id)
|
||||
if assert_false(ticket.is_exception()):
|
||||
return
|
||||
_on_party_ticket(ticket)
|
||||
|
||||
func _on_party_ticket(ticket : NakamaRTAPI.PartyMatchmakerTicket):
|
||||
prints("_on_party_ticket", ticket)
|
||||
var removed = await socket2.remove_matchmaker_party_async(ticket.party_id, ticket.ticket)
|
||||
if assert_false(removed.is_exception()):
|
||||
return
|
||||
var sent = await socket2.send_party_data_async(ticket.party_id, 1, "asd")
|
||||
if assert_false(sent.is_exception()):
|
||||
return
|
||||
|
||||
func _on_party_presence(party_presence : NakamaRTAPI.PartyPresenceEvent):
|
||||
prints("_on_party_presence", party_presence)
|
||||
var left = party_presence.leaves.size() == 1
|
||||
if left:
|
||||
var closed = await socket2.close_party_async(party_presence.party_id)
|
||||
if assert_false(closed.is_exception()):
|
||||
return
|
||||
done()
|
||||
@@ -0,0 +1,27 @@
|
||||
extends "res://base_test.gd"
|
||||
|
||||
var _connected = false
|
||||
|
||||
func setup():
|
||||
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||
|
||||
var session = await client.authenticate_custom_async("MyIdentifier")
|
||||
if assert_cond(session.is_valid()):
|
||||
return
|
||||
|
||||
var socket = Nakama.create_socket_from(client)
|
||||
socket.connected.connect(self._on_socket_connected)
|
||||
var conn = await socket.connect_async(session)
|
||||
# Check that connection succeded
|
||||
if assert_false(conn.is_exception()):
|
||||
return
|
||||
# Check that signal has been called
|
||||
if assert_cond(_connected):
|
||||
return
|
||||
done()
|
||||
|
||||
func _on_socket_connected():
|
||||
_connected = true
|
||||
|
||||
func _process(_delta):
|
||||
assert_time(3)
|
||||
@@ -0,0 +1,72 @@
|
||||
extends "res://base_test.gd"
|
||||
|
||||
const COLLECTION = "ACollection"
|
||||
const K1 = "Question"
|
||||
const K2 = "Answer"
|
||||
const V1 = {"question": "Ultimate question"}
|
||||
const V2 = {"answer": "42"}
|
||||
|
||||
func setup():
|
||||
var username = str(randi_range(1000, 100000))
|
||||
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||
|
||||
var session = await client.authenticate_custom_async("MyIdentifier")
|
||||
if assert_cond(session.is_valid()):
|
||||
return
|
||||
|
||||
var objs = [
|
||||
NakamaWriteStorageObject.new(COLLECTION, K1, 1, 1, to_json(V1), ""),
|
||||
NakamaWriteStorageObject.new(COLLECTION, K2, 1, 1, to_json(V2), "")
|
||||
]
|
||||
var write : NakamaAPI.ApiStorageObjectAcks = await client.write_storage_objects_async(session, objs)
|
||||
if assert_false(write.is_exception()):
|
||||
return
|
||||
|
||||
var objs_ids = []
|
||||
for a in write.acks:
|
||||
#var obj : NakamaAPI.ApiStorageObjectAck = a
|
||||
objs_ids.append(NakamaStorageObjectId.new(a.collection, a.key, a.user_id, a.version))
|
||||
|
||||
var read : NakamaAPI.ApiStorageObjects = await client.read_storage_objects_async(session, objs_ids)
|
||||
if assert_false(read.is_exception()):
|
||||
return
|
||||
if assert_equal(read.objects.size(), 2):
|
||||
return
|
||||
if assert_equal(read.objects[0].collection, COLLECTION):
|
||||
return
|
||||
if assert_cond(read.objects[0].key in [K1, K2]):
|
||||
return
|
||||
if assert_cond(to_json(parse_json(read.objects[0].value)) in [to_json(V1), to_json(V2)]):
|
||||
return
|
||||
|
||||
# Delete one
|
||||
var del = await client.delete_storage_objects_async(session, [objs_ids[0]])
|
||||
if assert_false(del.is_exception()):
|
||||
return
|
||||
|
||||
# Confirm that one was deleted
|
||||
var read2 : NakamaAPI.ApiStorageObjects = await client.read_storage_objects_async(session, objs_ids)
|
||||
if assert_false(read2.is_exception()):
|
||||
return
|
||||
if assert_equal(read2.objects.size(), 1):
|
||||
return
|
||||
if assert_equal(read2.objects[0].collection, COLLECTION):
|
||||
return
|
||||
if assert_equal(read2.objects[0].key, objs_ids[1].key):
|
||||
return
|
||||
if assert_cond(to_json(parse_json(read2.objects[0].value)) in [to_json(V1), to_json(V2)]):
|
||||
return
|
||||
done()
|
||||
return
|
||||
|
||||
func _process(_delta):
|
||||
assert_time(3)
|
||||
|
||||
func to_json(value) -> String:
|
||||
return JSON.stringify(value)
|
||||
|
||||
func parse_json(value):
|
||||
var json = JSON.new()
|
||||
if json.parse(value) != OK:
|
||||
return null
|
||||
return json.get_data()
|
||||
@@ -0,0 +1,22 @@
|
||||
extends Node
|
||||
|
||||
var HOST = "127.0.0.1"
|
||||
var PORT = 7350
|
||||
var SCHEME = "http"
|
||||
var SERVER_KEY = "defaultkey"
|
||||
|
||||
func _ready():
|
||||
var f = FileAccess.open("res://settings.json", FileAccess.READ)
|
||||
if not f:
|
||||
return
|
||||
var json = JSON.new()
|
||||
var error = json.parse(f.get_as_text())
|
||||
var parsed = json.get_data()
|
||||
if error != OK or typeof(parsed) != TYPE_DICTIONARY:
|
||||
return
|
||||
for k in parsed:
|
||||
match k:
|
||||
"HOST": HOST = parsed[k]
|
||||
"PORT": PORT = parsed[k]
|
||||
"SCHEME": SCHEME = parsed[k]
|
||||
"SERVER_KEY": SERVER_KEY = parsed[k]
|
||||
Reference in New Issue
Block a user