class_name LocalInputTimeline extends RefCounted # Client-only sequence/action timeline. It mirrors the stream the server's # InputJitterBuffer will consume: an attack fills its deliberate sequence gap # with repeat-last actions, while a release retransmits immutable data. const ShipActionScript = preload("res://scripts/ship_action.gd") const RETAINED_REDUNDANCY := 4 var latest_issued_seq := 0 var latest_applied_seq := -1 var configured := false var _actions := {} var _last_issued_action = ShipActionScript.new() var _last_applied_action = ShipActionScript.new() func configure_initial_delay(delay_ticks: int) -> void: if configured: return latest_applied_seq = -maxi(delay_ticks, 1) configured = true func issue(delta: int, intent) -> int: if delta <= 0: # An already-issued sequence may be in flight or consumed. Never mutate # it; carry current raw intent to the next unique command instead. return latest_issued_seq var from_seq := latest_issued_seq + 1 latest_issued_seq += delta for seq in range(from_seq, latest_issued_seq): _actions[seq] = _last_issued_action.copy() _actions[latest_issued_seq] = intent.copy() _last_issued_action = intent.copy() _prune_consumed_actions() return latest_issued_seq func consume() -> Dictionary: latest_applied_seq += 1 if _actions.has(latest_applied_seq): _last_applied_action = _actions[latest_applied_seq].copy() _prune_consumed_actions() return {"seq": latest_applied_seq, "action": _last_applied_action.copy()} # The action actually issued for a sequence, or null if it is no longer # retained. Returns a copy: the timeline's stored actions are immutable once # issued (see issue()), and handing out the live object would let a caller # break that from the outside. func action_for(seq: int): if not _actions.has(seq): return null return _actions[seq].copy() func packet_actions(max_count: int) -> Array: var out: Array = [] for seq in range(latest_issued_seq, maxi(0, latest_issued_seq - max_count), -1): if not _actions.has(seq): break out.append(_actions[seq].copy()) return out func retained_action_count() -> int: return _actions.size() func _prune_consumed_actions() -> void: # Preserve the local command needed for the server's redundancy window, # then discard actions that are older than both consumption and backup use. var keep_from := latest_issued_seq - RETAINED_REDUNDANCY + 1 for seq in _actions.keys(): if int(seq) < keep_from: _actions.erase(seq)