Ga naar hoofdinhoud

Synchronisatie

Data synchronisatie tussen client en server.

Overzicht

┌─────────────────────────────────────────────────────────────────┐
│ Sync Architectuur │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Client Server │
│ ────── ────── │
│ │
│ Local State Authoritative State │
│ ├── Optimistic Updates ├── Validation │
│ ├── Prediction ├── Persistence │
│ └── Rollback └── Broadcast │
│ │
│ Flow: │
│ 1. Client: Optimistic update (instant feedback) │
│ 2. Client: Send reducer call │
│ 3. Server: Validate & process │
│ 4. Server: Broadcast result │
│ 5. Client: Confirm or rollback │
│ │
└─────────────────────────────────────────────────────────────────┘

Position Sync

Player Position Broadcasting

# position_sync.gd
extends Node

const SYNC_INTERVAL: float = 0.1 # 100ms
const MIN_MOVE_DISTANCE: float = 5.0

var _sync_timer: float = 0.0
var _last_synced_position: Vector2 = Vector2.ZERO
var _last_synced_scene: String = ""

func _process(delta: float) -> void:
if not SpacetimeClient.is_connected:
return

_sync_timer += delta
if _sync_timer >= SYNC_INTERVAL:
_sync_timer = 0.0
_check_position_sync()

func _check_position_sync() -> void:
var current_pos = GameState.player_position
var current_scene = SceneManager.current_scene_name

# Scene changed?
if current_scene != _last_synced_scene:
_sync_position(current_pos, current_scene)
return

# Moved enough?
if current_pos.distance_to(_last_synced_position) >= MIN_MOVE_DISTANCE:
_sync_position(current_pos, current_scene)

func _sync_position(pos: Vector2, scene: String) -> void:
_last_synced_position = pos
_last_synced_scene = scene

SpacetimeDB.call_reducer("update_player_position", [
int(pos.x),
int(pos.y),
scene
])

Remote Player Interpolation

# remote_player.gd
extends Node2D

const INTERPOLATION_SPEED: float = 10.0

var target_position: Vector2 = Vector2.ZERO
var player_id: int = 0

func _process(delta: float) -> void:
# Smooth interpolation naar target position
position = position.lerp(target_position, INTERPOLATION_SPEED * delta)

# Animation based on movement
var velocity = target_position - position
if velocity.length() > 1.0:
_play_walk_animation(velocity.normalized())
else:
_play_idle_animation()

func update_position(new_pos: Vector2) -> void:
target_position = new_pos

func _play_walk_animation(direction: Vector2) -> void:
animated_sprite.play("walk")
animated_sprite.flip_h = direction.x < 0

Inventory Sync

Optimistic Updates

# inventory_manager.gd

func add_item(item_id: String, quantity: int) -> int:
# 1. Optimistic local update
var added = _add_item_local(item_id, quantity)

if added > 0:
# 2. Sync to server
SpacetimeDB.call_reducer("add_inventory_item", [item_id, added])

return added

func remove_item(item_id: String, quantity: int) -> bool:
# 1. Check locally
if not has_item(item_id, quantity):
return false

# 2. Optimistic removal
_remove_item_local(item_id, quantity)

# 3. Sync to server
SpacetimeDB.call_reducer("remove_inventory_item", [item_id, quantity])

return true

Server Confirmation

func _ready() -> void:
# Listen voor server updates
SpacetimeDB.InventoryItem.on_insert.connect(_on_item_inserted)
SpacetimeDB.InventoryItem.on_update.connect(_on_item_updated)
SpacetimeDB.InventoryItem.on_delete.connect(_on_item_deleted)

func _on_item_inserted(row: InventoryItem) -> void:
if row.player_id != GameState.player_id:
return

# Server bevestigt: update lokale state
var local_item = _find_local_item(row.item_id, row.container_id, row.slot_index)

if not local_item:
# Nieuw item van server (bijv. multiplayer gift)
_add_item_from_server(row)
NotificationManager.toast("Item ontvangen!", "success")

func _on_item_deleted(row: InventoryItem) -> void:
if row.player_id != GameState.player_id:
return

# Server bevestigt removal
_confirm_item_removal(row.item_id, row.container_id, row.slot_index)

Conflict Resolution

# Bij sync conflicten: server wins
func _reconcile_inventory() -> void:
# Haal complete inventory op van server
var server_items = SpacetimeDB.InventoryItem.filter_by_player_id(GameState.player_id)

# Reset lokale state
_clear_local_inventory()

# Rebuild from server
for item in server_items:
_add_item_from_server(item)

NotificationManager.toast("Inventory gesynchroniseerd", "info")

Farm Sync

Plot State Sync

# farm_manager.gd

func plant_seed(plot_index: int, seed_id: String) -> void:
# Optimistic update
var plot = _get_plot(plot_index)
plot.seed_id = seed_id
plot.growth_stage = 0
plot.planted_timestamp = Time.get_unix_time_from_system()
plot.update_visuals()

# Server sync
SpacetimeDB.call_reducer("plant_seed", [plot_index, seed_id])

func _on_farm_plot_updated(old: FarmPlotRow, new: FarmPlotRow) -> void:
if new.player_id != GameState.player_id:
return

var plot = _get_plot(new.plot_index)

# Update from server (handles multi-device sync)
if new.growth_stage > plot.growth_stage:
plot.growth_stage = new.growth_stage
plot.update_visuals()
plot.play_growth_effect()

Real-time Growth Updates

# Growth wordt server-side berekend
func _ready() -> void:
SpacetimeDB.FarmPlot.on_update.connect(_on_farm_plot_updated)

# Periodic check voor growth updates
var timer = Timer.new()
timer.wait_time = 30.0
timer.autostart = true
timer.timeout.connect(_request_growth_check)
add_child(timer)

func _request_growth_check() -> void:
SpacetimeDB.call_reducer("check_farm_growth", [])

Quest Sync

# quest_manager.gd

func _on_quest_progress_updated(old: PlayerQuestRow, new: PlayerQuestRow) -> void:
if new.player_id != GameState.player_id:
return

var quest_id = new.quest_id

# Update local state
if quest_id in active_quests:
var quest = active_quests[quest_id]
var old_progress = quest.objectives_progress.duplicate()

quest.objectives_progress = JSON.parse_string(new.objectives_json)
quest.status = new.status

# Check for new completions
for obj_id in quest.objectives_progress:
var old_val = old_progress.get(obj_id, 0)
var new_val = quest.objectives_progress[obj_id]

if new_val > old_val:
quest_progress.emit(quest_id, obj_id, new_val, _get_objective_target(quest_id, obj_id))

# Quest completed?
if new.status == "completed" and active_quests.has(quest_id):
_handle_quest_completed(quest_id)

Subscription Management

Efficient Subscriptions

# Alleen subscriben op relevante data
func _update_subscriptions_for_scene(scene_name: String) -> void:
# Unsubscribe van vorige scene data
_client.unsubscribe(_current_scene_subscription)

# Subscribe op nieuwe scene data
match scene_name:
"treehouse":
_current_scene_subscription = _client.subscribe([
"SELECT * FROM OnlinePlayer WHERE current_scene = 'treehouse'",
"SELECT * FROM FarmPlot WHERE player_id = :player_id"
])
"world_map":
_current_scene_subscription = _client.subscribe([
"SELECT * FROM OnlinePlayer WHERE current_scene = 'world_map'"
])
"club_hall":
_current_scene_subscription = _client.subscribe([
"SELECT * FROM OnlinePlayer WHERE club_id = :club_id",
"SELECT * FROM ClubProject WHERE club_id = :club_id"
])

Subscription Batching

# Batch multiple subscriptions
func setup_initial_subscriptions() -> void:
_client.subscribe([
# Player data
"SELECT * FROM Player WHERE identity = :identity",
"SELECT * FROM PlayerCharacter WHERE player_id = :player_id",

# Inventory
"SELECT * FROM Container WHERE player_id = :player_id",
"SELECT * FROM InventoryItem WHERE player_id = :player_id",

# Social
"SELECT * FROM Friend WHERE player_id = :player_id OR friend_id = :player_id",
"SELECT * FROM FriendRequest WHERE target_id = :player_id",

# Quests
"SELECT * FROM PlayerQuest WHERE player_id = :player_id AND status = 'active'",

# Chat (recent only)
"SELECT * FROM ChatMessage WHERE timestamp > :recent_timestamp ORDER BY timestamp DESC LIMIT 100"
])

Delta Compression

# Alleen wijzigingen sturen, niet volledige state
func _sync_character_appearance(changes: Dictionary) -> void:
if changes.is_empty():
return

# Stuur alleen gewijzigde velden
SpacetimeDB.call_reducer("update_character_appearance", [
JSON.stringify(changes)
])

# Voorbeeld: alleen haar kleur veranderd
# changes = {"hair_color": "#FF5500"}
# In plaats van hele character data

Latency Compensation

# Timestamp-based ordering
func _on_chat_message_received(row: ChatMessage) -> void:
# Insert op juiste positie gebaseerd op server timestamp
var insert_index = _find_insert_index(row.timestamp)
_chat_messages.insert(insert_index, row)
_update_chat_ui()

func _find_insert_index(timestamp: int) -> int:
for i in range(_chat_messages.size() - 1, -1, -1):
if _chat_messages[i].timestamp <= timestamp:
return i + 1
return 0

Volgende