Save and open worlds in Local Storage #2

Closed
opened 2026-09-25 08:47:43 +02:00 by kyuubi · 4 comments
Owner

Save worlds into Local Storage as .resonitepackage files and open them again. Only needs to work with the mod installed: vanilla's package importer refuses non-object packages.

Design

  • Package: main record RecordType = "world" with name, description, tags and the world thumbnail embedded; main asset is the world graph from World.SaveWorld(), assets collected into the package the same way as items.
  • Save new world: "Save As" / "Save Copy" → "Save to Inventory" while Local Storage is open. Hook: prefix on the private InventoryBrowser.AddItem(Slot, bool, RecordDirectory) for local directories whose item is a WorldOrbSaver. Vanilla would call Userspace.SaveWorld, which throws on the L- owner id.
  • Overwrite save: a world opened from Local Storage keeps a local CorrespondingRecord whose AssetURI is its package, so "Save" rewrites that package. Hooks: Userspace.CanSave (also gives the host the Owner role) and Userspace.SaveWorldTaskIntern.
  • Inventory entry: listed as an object record tagged world_orb + world_url:<package uri>, so the inventory shows "Open World"; spawning it creates a world orb pointing at the package.
  • Open: Userspace.LoadWorld recognises resolocalstorage:/// URIs, imports the package assets into the local DB (as PackageImporter does) and starts a session from the graph. Userspace.OpenWorld gets a no-op InitWorld for these URIs to pass its URL-scheme guard.
  • Own URI scheme rather than resrec:///L-…: a world orb with a record URL fetches it from the cloud on touch and destroys itself on 404.

Plan

  • Package asset importer shared by world loading (mirror of PackageImporter's remap loop)
  • World save (new + overwrite) and world package listing / spawn-as-orb
  • World open via LoadWorld
  • Unit test: world record round-trips through ItemPackage
  • Build, tests, install, commit + push
  • In-game check: save a world to Local Storage, open it, edit, Save, reopen

Limits

  • Other users can join a local world through its session like any hosted world, but can't open the package themselves.
  • Overwrite saves replace the package in place; older versions are not kept.
Save worlds into Local Storage as `.resonitepackage` files and open them again. Only needs to work with the mod installed: vanilla's package importer refuses non-object packages. ## Design - **Package:** main record `RecordType = "world"` with name, description, tags and the world thumbnail embedded; main asset is the world graph from `World.SaveWorld()`, assets collected into the package the same way as items. - **Save new world:** "Save As" / "Save Copy" → "Save to Inventory" while Local Storage is open. Hook: prefix on the private `InventoryBrowser.AddItem(Slot, bool, RecordDirectory)` for local directories whose item is a `WorldOrbSaver`. Vanilla would call `Userspace.SaveWorld`, which throws on the `L-` owner id. - **Overwrite save:** a world opened from Local Storage keeps a local `CorrespondingRecord` whose `AssetURI` is its package, so "Save" rewrites that package. Hooks: `Userspace.CanSave` (also gives the host the Owner role) and `Userspace.SaveWorldTaskIntern`. - **Inventory entry:** listed as an object record tagged `world_orb` + `world_url:<package uri>`, so the inventory shows "Open World"; spawning it creates a world orb pointing at the package. - **Open:** `Userspace.LoadWorld` recognises `resolocalstorage:///` URIs, imports the package assets into the local DB (as `PackageImporter` does) and starts a session from the graph. `Userspace.OpenWorld` gets a no-op `InitWorld` for these URIs to pass its URL-scheme guard. - Own URI scheme rather than `resrec:///L-…`: a world orb with a record URL fetches it from the cloud on touch and destroys itself on 404. ## Plan - [x] Package asset importer shared by world loading (mirror of `PackageImporter`'s remap loop) - [x] World save (new + overwrite) and world package listing / spawn-as-orb - [x] World open via `LoadWorld` - [x] Unit test: world record round-trips through `ItemPackage` - [x] Build, tests, install, commit + push - [x] In-game check: save a world to Local Storage, open it, edit, Save, reopen ## Limits - Other users can join a local world through its session like any hosted world, but can't open the package themselves. - Overwrite saves replace the package in place; older versions are not kept.
Author
Owner

Implemented in dc78250 and hot-loaded into the running game (Resonite 2026.9.18.82). v1 unloaded cleanly (Harmony UnpatchAll) and the new build loaded with no errors. 27 unit tests pass. The in-game check (save a world, open it, edit, Save, reopen) is still open. Side note from the logs: Resdb Tools throws 'Invalid owner type: L-ResoLocalStorage' when it tries to fetch a selected local item from the cloud. That is harmless and comes from that mod.

Implemented in dc78250 and hot-loaded into the running game (Resonite 2026.9.18.82). v1 unloaded cleanly (Harmony UnpatchAll) and the new build loaded with no errors. 27 unit tests pass. The in-game check (save a world, open it, edit, Save, reopen) is still open. Side note from the logs: Resdb Tools throws 'Invalid owner type: L-ResoLocalStorage' when it tries to fetch a selected local item from the cloud. That is harmless and comes from that mod.
Author
Owner

In-game testing found two issues, both fixed in 4992a44 (v0.2.1):

  • Open World from the inventory did nothing. The link was stored via Uri.ToString(), which unescapes, and vanilla drops links that fail Uri.IsWellFormedUriString. Non-ASCII paths never pass that check, so InventoryBrowser.OnOpenWorld is now patched for local links. Spawning and clicking the orb already worked.
  • The inventory tile showed the flat world screenshot. Vanilla shows a render of the world orb. The save now turns the saver into an orb (name + creator) and renders it through ItemHelper.SaveItem, stored as an extra R-Inventory record.

Also: all skip-the-original prefixes now respect __runOriginal (Restrainite's world-switch block was bypassed), and local orbs no longer ask the cloud about their link. Worlds saved with 0.2.0 still show the flat thumbnail until saved again.

In-game testing found two issues, both fixed in 4992a44 (v0.2.1): - **Open World from the inventory did nothing.** The link was stored via `Uri.ToString()`, which unescapes, and vanilla drops links that fail `Uri.IsWellFormedUriString`. Non-ASCII paths never pass that check, so `InventoryBrowser.OnOpenWorld` is now patched for local links. Spawning and clicking the orb already worked. - **The inventory tile showed the flat world screenshot.** Vanilla shows a render of the world orb. The save now turns the saver into an orb (name + creator) and renders it through `ItemHelper.SaveItem`, stored as an extra `R-Inventory` record. Also: all skip-the-original prefixes now respect `__runOriginal` (Restrainite's world-switch block was bypassed), and local orbs no longer ask the cloud about their link. Worlds saved with 0.2.0 still show the flat thumbnail until saved again.
Author
Owner

Bug from testing, fixed in e911172 (v0.2.2): while a big world saved, the desktop cursor stayed unlocked. 0.2.1 deactivated the saver's slot while its context menu was still closing. ContextMenu only releases its cursor-unlock registration in OnChanges once it's fully closed, so the frozen menu kept the cursor free until the saver was destroyed. The saver now stays active, and the orb render happens at the end of the package write.

Bug from testing, fixed in e911172 (v0.2.2): while a big world saved, the desktop cursor stayed unlocked. 0.2.1 deactivated the saver's slot while its context menu was still closing. `ContextMenu` only releases its cursor-unlock registration in `OnChanges` once it's fully closed, so the frozen menu kept the cursor free until the saver was destroyed. The saver now stays active, and the orb render happens at the end of the package write.
Author
Owner

Testing a 1.2 GB world (Compact Cloud Home) showed the saver orb staying in the user's face for the whole two-minute write. Since e9c4f02, saving follows vanilla again: the saver shows "Saving..." only during the capture, turns into the orb, is rendered and animates away, and the package is written in the background with progress in the dash (#3). Destroying the saver also removes its context menu, so the cursor is released.

Testing a 1.2 GB world (Compact Cloud Home) showed the saver orb staying in the user's face for the whole two-minute write. Since e9c4f02, saving follows vanilla again: the saver shows "Saving..." only during the capture, turns into the orb, is rendered and animates away, and the package is written in the background with progress in the dash (#3). Destroying the saver also removes its context menu, so the cursor is released.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kyuubi/ResoLocalStorage#2
No description provided.