NEO-132: queue economy sync while clients are busy
Gather/craft start inventory and skill GETs in parallel with quest-progress; post-completion _refresh_economy_hud was dropped when those clients were _busy. Match quest_progress_client pending-sync flush so a follow-up GET runs after the in-flight request completes.pull/171/head
parent
acf3d079d2
commit
e2991a2926
|
|
@ -13,6 +13,7 @@ const SCHEMA_VERSION := 1
|
||||||
|
|
||||||
var _http: Node
|
var _http: Node
|
||||||
var _busy: bool = false
|
var _busy: bool = false
|
||||||
|
var _sync_pending: bool = false
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
@ -29,7 +30,12 @@ func _ready() -> void:
|
||||||
|
|
||||||
func request_sync_from_server() -> void:
|
func request_sync_from_server() -> void:
|
||||||
if _busy:
|
if _busy:
|
||||||
|
_sync_pending = true
|
||||||
return
|
return
|
||||||
|
_start_sync_request()
|
||||||
|
|
||||||
|
|
||||||
|
func _start_sync_request() -> void:
|
||||||
_busy = true
|
_busy = true
|
||||||
var url := "%s/game/players/%s/inventory" % [_base_root(), _player_path_segment()]
|
var url := "%s/game/players/%s/inventory" % [_base_root(), _player_path_segment()]
|
||||||
var err: Error = _http.request(url)
|
var err: Error = _http.request(url)
|
||||||
|
|
@ -38,6 +44,14 @@ func request_sync_from_server() -> void:
|
||||||
push_warning("InventoryClient: %s" % reason)
|
push_warning("InventoryClient: %s" % reason)
|
||||||
_busy = false
|
_busy = false
|
||||||
inventory_sync_failed.emit(reason)
|
inventory_sync_failed.emit(reason)
|
||||||
|
_try_flush_pending_sync()
|
||||||
|
|
||||||
|
|
||||||
|
func _try_flush_pending_sync() -> void:
|
||||||
|
if not _sync_pending or _busy:
|
||||||
|
return
|
||||||
|
_sync_pending = false
|
||||||
|
_start_sync_request()
|
||||||
|
|
||||||
|
|
||||||
func _base_root() -> String:
|
func _base_root() -> String:
|
||||||
|
|
@ -66,16 +80,19 @@ func _on_request_completed(
|
||||||
var reason := "HTTP failed (result=%s)" % result
|
var reason := "HTTP failed (result=%s)" % result
|
||||||
push_warning("InventoryClient: %s" % reason)
|
push_warning("InventoryClient: %s" % reason)
|
||||||
inventory_sync_failed.emit(reason)
|
inventory_sync_failed.emit(reason)
|
||||||
|
_try_flush_pending_sync()
|
||||||
return
|
return
|
||||||
if response_code == 404:
|
if response_code == 404:
|
||||||
var reason404 := "HTTP 404 (player unknown)"
|
var reason404 := "HTTP 404 (player unknown)"
|
||||||
push_warning("InventoryClient: %s" % reason404)
|
push_warning("InventoryClient: %s" % reason404)
|
||||||
inventory_sync_failed.emit(reason404)
|
inventory_sync_failed.emit(reason404)
|
||||||
|
_try_flush_pending_sync()
|
||||||
return
|
return
|
||||||
if response_code < 200 or response_code >= 300:
|
if response_code < 200 or response_code >= 300:
|
||||||
var reason_code := "HTTP %s" % response_code
|
var reason_code := "HTTP %s" % response_code
|
||||||
push_warning("InventoryClient: %s" % reason_code)
|
push_warning("InventoryClient: %s" % reason_code)
|
||||||
inventory_sync_failed.emit(reason_code)
|
inventory_sync_failed.emit(reason_code)
|
||||||
|
_try_flush_pending_sync()
|
||||||
return
|
return
|
||||||
var text := body.get_string_from_utf8()
|
var text := body.get_string_from_utf8()
|
||||||
var snapshot: Variant = parse_inventory_json(text)
|
var snapshot: Variant = parse_inventory_json(text)
|
||||||
|
|
@ -83,5 +100,7 @@ func _on_request_completed(
|
||||||
var reason_json := "non-JSON body or schemaVersion mismatch"
|
var reason_json := "non-JSON body or schemaVersion mismatch"
|
||||||
push_warning("InventoryClient: %s" % reason_json)
|
push_warning("InventoryClient: %s" % reason_json)
|
||||||
inventory_sync_failed.emit(reason_json)
|
inventory_sync_failed.emit(reason_json)
|
||||||
|
_try_flush_pending_sync()
|
||||||
return
|
return
|
||||||
inventory_received.emit(snapshot as Dictionary)
|
inventory_received.emit(snapshot as Dictionary)
|
||||||
|
_try_flush_pending_sync()
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ const SCHEMA_VERSION := 1
|
||||||
|
|
||||||
var _http: Node
|
var _http: Node
|
||||||
var _busy: bool = false
|
var _busy: bool = false
|
||||||
|
var _sync_pending: bool = false
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
|
|
@ -29,7 +30,12 @@ func _ready() -> void:
|
||||||
|
|
||||||
func request_sync_from_server() -> void:
|
func request_sync_from_server() -> void:
|
||||||
if _busy:
|
if _busy:
|
||||||
|
_sync_pending = true
|
||||||
return
|
return
|
||||||
|
_start_sync_request()
|
||||||
|
|
||||||
|
|
||||||
|
func _start_sync_request() -> void:
|
||||||
_busy = true
|
_busy = true
|
||||||
var url := "%s/game/players/%s/skill-progression" % [_base_root(), _player_path_segment()]
|
var url := "%s/game/players/%s/skill-progression" % [_base_root(), _player_path_segment()]
|
||||||
var err: Error = _http.request(url)
|
var err: Error = _http.request(url)
|
||||||
|
|
@ -38,6 +44,14 @@ func request_sync_from_server() -> void:
|
||||||
push_warning("SkillProgressionClient: %s" % reason)
|
push_warning("SkillProgressionClient: %s" % reason)
|
||||||
_busy = false
|
_busy = false
|
||||||
progression_sync_failed.emit(reason)
|
progression_sync_failed.emit(reason)
|
||||||
|
_try_flush_pending_sync()
|
||||||
|
|
||||||
|
|
||||||
|
func _try_flush_pending_sync() -> void:
|
||||||
|
if not _sync_pending or _busy:
|
||||||
|
return
|
||||||
|
_sync_pending = false
|
||||||
|
_start_sync_request()
|
||||||
|
|
||||||
|
|
||||||
func skill_row(skill_id: String, snapshot: Dictionary = {}) -> Dictionary:
|
func skill_row(skill_id: String, snapshot: Dictionary = {}) -> Dictionary:
|
||||||
|
|
@ -82,16 +96,19 @@ func _on_request_completed(
|
||||||
var reason := "HTTP failed (result=%s)" % result
|
var reason := "HTTP failed (result=%s)" % result
|
||||||
push_warning("SkillProgressionClient: %s" % reason)
|
push_warning("SkillProgressionClient: %s" % reason)
|
||||||
progression_sync_failed.emit(reason)
|
progression_sync_failed.emit(reason)
|
||||||
|
_try_flush_pending_sync()
|
||||||
return
|
return
|
||||||
if response_code == 404:
|
if response_code == 404:
|
||||||
var reason404 := "HTTP 404 (player unknown)"
|
var reason404 := "HTTP 404 (player unknown)"
|
||||||
push_warning("SkillProgressionClient: %s" % reason404)
|
push_warning("SkillProgressionClient: %s" % reason404)
|
||||||
progression_sync_failed.emit(reason404)
|
progression_sync_failed.emit(reason404)
|
||||||
|
_try_flush_pending_sync()
|
||||||
return
|
return
|
||||||
if response_code < 200 or response_code >= 300:
|
if response_code < 200 or response_code >= 300:
|
||||||
var reason_code := "HTTP %s" % response_code
|
var reason_code := "HTTP %s" % response_code
|
||||||
push_warning("SkillProgressionClient: %s" % reason_code)
|
push_warning("SkillProgressionClient: %s" % reason_code)
|
||||||
progression_sync_failed.emit(reason_code)
|
progression_sync_failed.emit(reason_code)
|
||||||
|
_try_flush_pending_sync()
|
||||||
return
|
return
|
||||||
var text := body.get_string_from_utf8()
|
var text := body.get_string_from_utf8()
|
||||||
var snapshot: Variant = parse_progression_json(text)
|
var snapshot: Variant = parse_progression_json(text)
|
||||||
|
|
@ -99,5 +116,7 @@ func _on_request_completed(
|
||||||
var reason_json := "non-JSON body or schemaVersion mismatch"
|
var reason_json := "non-JSON body or schemaVersion mismatch"
|
||||||
push_warning("SkillProgressionClient: %s" % reason_json)
|
push_warning("SkillProgressionClient: %s" % reason_json)
|
||||||
progression_sync_failed.emit(reason_json)
|
progression_sync_failed.emit(reason_json)
|
||||||
|
_try_flush_pending_sync()
|
||||||
return
|
return
|
||||||
progression_received.emit(snapshot as Dictionary)
|
progression_received.emit(snapshot as Dictionary)
|
||||||
|
_try_flush_pending_sync()
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,33 @@ class MockHttpTransport:
|
||||||
return OK
|
return OK
|
||||||
|
|
||||||
|
|
||||||
|
class PendingSyncHttpTransport:
|
||||||
|
extends Node
|
||||||
|
signal request_completed(
|
||||||
|
result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray
|
||||||
|
)
|
||||||
|
var request_count: int = 0
|
||||||
|
var response_code: int = 200
|
||||||
|
var body_json: String = ""
|
||||||
|
|
||||||
|
func request(
|
||||||
|
_url: String,
|
||||||
|
_custom_headers: PackedStringArray = PackedStringArray(),
|
||||||
|
_method: HTTPClient.Method = HTTPClient.METHOD_GET,
|
||||||
|
_request_data: String = ""
|
||||||
|
) -> Error:
|
||||||
|
request_count += 1
|
||||||
|
return OK
|
||||||
|
|
||||||
|
func complete_pending() -> void:
|
||||||
|
request_completed.emit(
|
||||||
|
HTTPRequest.RESULT_SUCCESS,
|
||||||
|
response_code,
|
||||||
|
PackedStringArray(),
|
||||||
|
body_json.to_utf8_buffer()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
static func _empty_inventory_json() -> String:
|
static func _empty_inventory_json() -> String:
|
||||||
var bag_parts: PackedStringArray = PackedStringArray()
|
var bag_parts: PackedStringArray = PackedStringArray()
|
||||||
for i in range(24):
|
for i in range(24):
|
||||||
|
|
@ -111,3 +138,17 @@ func test_malformed_json_emits_inventory_sync_failed() -> void:
|
||||||
c.call("request_sync_from_server")
|
c.call("request_sync_from_server")
|
||||||
# Assert
|
# Assert
|
||||||
await assert_signal(c).is_emitted("inventory_sync_failed", any())
|
await assert_signal(c).is_emitted("inventory_sync_failed", any())
|
||||||
|
|
||||||
|
|
||||||
|
func test_request_sync_queues_while_busy_and_flushes_after_completion() -> void:
|
||||||
|
# Arrange
|
||||||
|
var transport := PendingSyncHttpTransport.new()
|
||||||
|
transport.body_json = _empty_inventory_json()
|
||||||
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
|
c.call("request_sync_from_server")
|
||||||
|
c.call("request_sync_from_server")
|
||||||
|
# Assert
|
||||||
|
assert_that(transport.request_count).is_equal(1)
|
||||||
|
transport.complete_pending()
|
||||||
|
assert_that(transport.request_count).is_equal(2)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ extends GdUnitTestSuite
|
||||||
const QuestHudController := preload("res://scripts/quest_hud_controller.gd")
|
const QuestHudController := preload("res://scripts/quest_hud_controller.gd")
|
||||||
const QuestDefinitionsClient := preload("res://scripts/quest_definitions_client.gd")
|
const QuestDefinitionsClient := preload("res://scripts/quest_definitions_client.gd")
|
||||||
const ItemDefsClient := preload("res://scripts/item_definitions_client.gd")
|
const ItemDefsClient := preload("res://scripts/item_definitions_client.gd")
|
||||||
|
const InventoryClient := preload("res://scripts/inventory_client.gd")
|
||||||
|
const SkillProgressionClient := preload("res://scripts/skill_progression_client.gd")
|
||||||
|
|
||||||
const OPERATOR_CHAIN_QUEST_ID := "prototype_quest_operator_chain"
|
const OPERATOR_CHAIN_QUEST_ID := "prototype_quest_operator_chain"
|
||||||
|
|
||||||
|
|
@ -47,6 +49,29 @@ class MockEconomySyncClient:
|
||||||
sync_count += 1
|
sync_count += 1
|
||||||
|
|
||||||
|
|
||||||
|
class PendingSyncHttpTransport:
|
||||||
|
extends Node
|
||||||
|
signal request_completed(
|
||||||
|
result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray
|
||||||
|
)
|
||||||
|
var request_count: int = 0
|
||||||
|
var body_json: String = ""
|
||||||
|
|
||||||
|
func request(
|
||||||
|
_url: String,
|
||||||
|
_custom_headers: PackedStringArray = PackedStringArray(),
|
||||||
|
_method: HTTPClient.Method = HTTPClient.METHOD_GET,
|
||||||
|
_request_data: String = ""
|
||||||
|
) -> Error:
|
||||||
|
request_count += 1
|
||||||
|
return OK
|
||||||
|
|
||||||
|
func complete_pending() -> void:
|
||||||
|
request_completed.emit(
|
||||||
|
HTTPRequest.RESULT_SUCCESS, 200, PackedStringArray(), body_json.to_utf8_buffer()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
func _make_defs_client(transport: Node) -> Node:
|
func _make_defs_client(transport: Node) -> Node:
|
||||||
var c: Node = QuestDefinitionsClient.new()
|
var c: Node = QuestDefinitionsClient.new()
|
||||||
c.set("injected_http", transport)
|
c.set("injected_http", transport)
|
||||||
|
|
@ -330,3 +355,47 @@ func test_boot_completed_does_not_emit_economy_refresh_signal() -> void:
|
||||||
await (assert_signal(controller).wait_until(200).is_not_emitted(
|
await (assert_signal(controller).wait_until(200).is_not_emitted(
|
||||||
"quest_completion_reward_transition"
|
"quest_completion_reward_transition"
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
func test_completion_transition_queues_economy_refresh_while_clients_busy() -> void:
|
||||||
|
# Arrange
|
||||||
|
var inv_transport := PendingSyncHttpTransport.new()
|
||||||
|
var bag_parts: PackedStringArray = PackedStringArray()
|
||||||
|
for i in range(24):
|
||||||
|
bag_parts.append('{"slotIndex":%d,"quantity":0}' % i)
|
||||||
|
inv_transport.body_json = (
|
||||||
|
'{"schemaVersion":1,"playerId":"dev-local-1","bagSlots":[%s],'
|
||||||
|
+ '"equipmentSlots":[{"slotIndex":0,"quantity":0}]}'
|
||||||
|
) % ", ".join(bag_parts)
|
||||||
|
var skill_transport := PendingSyncHttpTransport.new()
|
||||||
|
skill_transport.body_json = (
|
||||||
|
'{"schemaVersion":1,"playerId":"dev-local-1","skills":'
|
||||||
|
+ '[{"id":"salvage","xp":0,"level":1}]}'
|
||||||
|
)
|
||||||
|
var inventory_client: Node = InventoryClient.new()
|
||||||
|
var skill_client: Node = SkillProgressionClient.new()
|
||||||
|
inventory_client.set("injected_http", inv_transport)
|
||||||
|
skill_client.set("injected_http", skill_transport)
|
||||||
|
var controller: Node = QuestHudController.new()
|
||||||
|
auto_free(inv_transport)
|
||||||
|
auto_free(skill_transport)
|
||||||
|
auto_free(inventory_client)
|
||||||
|
auto_free(skill_client)
|
||||||
|
auto_free(controller)
|
||||||
|
add_child(inventory_client)
|
||||||
|
add_child(skill_client)
|
||||||
|
add_child(controller)
|
||||||
|
controller.set("_inventory_client", inventory_client)
|
||||||
|
controller.set("_skill_progression_client", skill_client)
|
||||||
|
inventory_client.call("request_sync_from_server")
|
||||||
|
skill_client.call("request_sync_from_server")
|
||||||
|
# Act
|
||||||
|
controller.call("_on_progress_received", _active_gather_snapshot())
|
||||||
|
controller.call("_on_progress_received", _completed_gather_with_summary_snapshot())
|
||||||
|
# Assert
|
||||||
|
assert_int(inv_transport.request_count).is_equal(1)
|
||||||
|
assert_int(skill_transport.request_count).is_equal(1)
|
||||||
|
inv_transport.complete_pending()
|
||||||
|
skill_transport.complete_pending()
|
||||||
|
assert_int(inv_transport.request_count).is_equal(2)
|
||||||
|
assert_int(skill_transport.request_count).is_equal(2)
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,33 @@ class MockHttpTransport:
|
||||||
return OK
|
return OK
|
||||||
|
|
||||||
|
|
||||||
|
class PendingSyncHttpTransport:
|
||||||
|
extends Node
|
||||||
|
signal request_completed(
|
||||||
|
result: int, response_code: int, headers: PackedStringArray, body: PackedByteArray
|
||||||
|
)
|
||||||
|
var request_count: int = 0
|
||||||
|
var response_code: int = 200
|
||||||
|
var body_json: String = ""
|
||||||
|
|
||||||
|
func request(
|
||||||
|
_url: String,
|
||||||
|
_custom_headers: PackedStringArray = PackedStringArray(),
|
||||||
|
_method: HTTPClient.Method = HTTPClient.METHOD_GET,
|
||||||
|
_request_data: String = ""
|
||||||
|
) -> Error:
|
||||||
|
request_count += 1
|
||||||
|
return OK
|
||||||
|
|
||||||
|
func complete_pending() -> void:
|
||||||
|
request_completed.emit(
|
||||||
|
HTTPRequest.RESULT_SUCCESS,
|
||||||
|
response_code,
|
||||||
|
PackedStringArray(),
|
||||||
|
body_json.to_utf8_buffer()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
static func _trio_progression_json() -> String:
|
static func _trio_progression_json() -> String:
|
||||||
return (
|
return (
|
||||||
'{"schemaVersion":1,"playerId":"dev-local-1","skills":['
|
'{"schemaVersion":1,"playerId":"dev-local-1","skills":['
|
||||||
|
|
@ -95,3 +122,17 @@ func test_http_404_emits_progression_sync_failed() -> void:
|
||||||
c.call("request_sync_from_server")
|
c.call("request_sync_from_server")
|
||||||
# Assert
|
# Assert
|
||||||
await assert_signal(c).is_emitted("progression_sync_failed", "HTTP 404 (player unknown)")
|
await assert_signal(c).is_emitted("progression_sync_failed", "HTTP 404 (player unknown)")
|
||||||
|
|
||||||
|
|
||||||
|
func test_request_sync_queues_while_busy_and_flushes_after_completion() -> void:
|
||||||
|
# Arrange
|
||||||
|
var transport := PendingSyncHttpTransport.new()
|
||||||
|
transport.body_json = _trio_progression_json()
|
||||||
|
var c := _make_client(transport)
|
||||||
|
# Act
|
||||||
|
c.call("request_sync_from_server")
|
||||||
|
c.call("request_sync_from_server")
|
||||||
|
# Assert
|
||||||
|
assert_that(transport.request_count).is_equal(1)
|
||||||
|
transport.complete_pending()
|
||||||
|
assert_that(transport.request_count).is_equal(2)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue