From f80d10d800aff2efe2c6da48dad6a44002007cb8 Mon Sep 17 00:00:00 2001 From: David Durieux Date: Tue, 26 May 2026 01:33:02 +0200 Subject: [PATCH] Add update object in persistence --- persistence/src/cache/mod.rs | 18 +++++++ persistence/src/websocket/handlers.rs | 68 ++++++++++++++++++++++++++- persistence/src/websocket/messages.rs | 10 ++++ 3 files changed, 95 insertions(+), 1 deletion(-) diff --git a/persistence/src/cache/mod.rs b/persistence/src/cache/mod.rs index d2799d9..63ef579 100644 --- a/persistence/src/cache/mod.rs +++ b/persistence/src/cache/mod.rs @@ -76,6 +76,24 @@ impl DualCache { } } + /// Look up an item by uuid. Checks the active map first, then the idle map. + /// + /// Checking the idle map covers the window between a `swap_and_drain` flip + /// and the subsequent `idle.clear()`, where an item may not yet be in the + /// new active map. + pub fn get(&self, uuid: &str) -> Option { + if let Some(item) = self.active_map().get(uuid) { + return Some(item.clone()); + } + // Idle map is whichever map is NOT currently active. + let idle = if self.active.load(Ordering::Acquire) { + &self.map_a + } else { + &self.map_b + }; + idle.get(uuid).map(|r| r.value().clone()) + } + /// Number of items currently waiting in the active map. pub fn pending_count(&self) -> usize { self.active_map().len() diff --git a/persistence/src/websocket/handlers.rs b/persistence/src/websocket/handlers.rs index dab7d3a..d3deb27 100644 --- a/persistence/src/websocket/handlers.rs +++ b/persistence/src/websocket/handlers.rs @@ -12,7 +12,7 @@ use crate::{ cache::DualCache, config::Config, db::queries::{get_all_items, get_item_by_uuid, Queries}, - websocket::messages::{BridgeEventEnvelope, GenericPropsRequest, Item}, + websocket::messages::{BridgeEventEnvelope, GenericPropsRequest, Item, UpdateObjectRequest}, }; /// Handle a single WebSocket connection for its entire lifetime. @@ -77,6 +77,72 @@ pub async fn handle_socket( } } } + "update_object" | "update_object_from_external" => { + match serde_json::from_value::(envelope.payload) { + Ok(req) => { + let mut incoming_map = req.object_data + .as_object() + .cloned() + .unwrap_or_default(); + // Extract special fields from the incoming partial data. + let new_parent_id = incoming_map + .remove("parent_id") + .and_then(|v| v.as_str().map(str::to_owned)); + let new_scenename = incoming_map + .remove("scenename") + .and_then(|v| v.as_str().map(str::to_owned)); + let new_position = incoming_map + .remove("position") + .and_then(|v| serde_json::from_value(v).ok()); + let new_rotation = incoming_map + .remove("rotation") + .and_then(|v| serde_json::from_value(v).ok()); + + // Fetch current state: cache first (covers items not yet flushed + // to DB), then DB (covers items not in cache). + let existing = match cache.get(&req.object_uuid) { + Some(item) => Some(item), + None => match get_item_by_uuid(&session, &queries, &req.object_uuid).await { + Ok(opt) => opt, + Err(e) => { + error!("{}: DB lookup error for uuid={}: {e}", envelope.name, req.object_uuid); + continue; + } + }, + }; + + let Some(base) = existing else { + warn!("{}: uuid={} not found in cache or DB, skipping", envelope.name, req.object_uuid); + continue; + }; + + // Merge: start from existing object_data, then overwrite with + // incoming fields so that untouched fields are preserved. + let mut merged_data = base + .object_data + .first() + .and_then(|v| v.as_object()) + .cloned() + .unwrap_or_default(); + merged_data.extend(incoming_map); + + let merged = Item { + uuid: base.uuid, + object_type: req.object_type, + parent_id: new_parent_id.or(base.parent_id), + scenename: new_scenename.or(base.scenename), + position: new_position.or(base.position), + rotation: new_rotation.or(base.rotation), + object_data: vec![Value::Object(merged_data)], + }; + debug!("{}: merged update for uuid={} type={}", envelope.name, merged.uuid, merged.object_type); + cache.insert(merged); + } + Err(e) => { + error!("{}: invalid payload: {e}", envelope.name); + } + } + } "create_object" | "create_object_from_gameserver" => { match serde_json::from_value::(envelope.payload) { Ok(req) => { diff --git a/persistence/src/websocket/messages.rs b/persistence/src/websocket/messages.rs index b282c50..abd64e1 100644 --- a/persistence/src/websocket/messages.rs +++ b/persistence/src/websocket/messages.rs @@ -33,6 +33,16 @@ pub struct GenericPropsRequest { pub object_data: serde_json::Value, } +/// Incoming request payload for update_object / update_object_from_external events. +/// `object_data` is **partial** — only the fields that changed are present. +/// `object_uuid` and `object_type` are always present at the same level. +#[derive(Debug, Deserialize)] +pub struct UpdateObjectRequest { + pub object_type: String, + pub object_uuid: String, + pub object_data: serde_json::Value, +} + // ─── REST request messages ───────────────────────────────────────────────── /// Request body for PUT /items/{uuid}.