Files
storyteller-bevy/studio/src/persistance/mod.rs
T
Danny McGee 6ca2a992e5 Storyteller.ai API integration (#49)
This PR integrates asset retrieval from the Storyteller.ai back-end API,
and begins implementing long-term scene saving and storage.

### Media retrieval
Instead of fetching raw asset URLs, going forward, media files from
remote sources (e.g., in URL parameters) should be specified with a path
like the following:
```
remote://<token>.<filetype>
```
where
* `token` is a media token (e.g., "m_70b77neq9jsgmy5v31mekzchcbd223")
* `filetype` is a file extension appropriate for the type of file being
  fetched (e.g., "gltf")

The `remote` protocol will be recognized by a [custom asset
reader](https://github.com/storytold/storyteller-bevy/blob/70726db111f6ccafd47feb055c5546540fef1786/studio/src/remote/reader.rs)
which will retrieve the file by calling a Storyteller API endpoint.

### Scene storage
Additionally, this PR begins laying the groundwork for
storing/retrieving Bevy scenes to/from the backend instead of solely
in-memory. This work is still incomplete -- as of this PR, a button has
been added to the web UI allowing the user to download the scene
file locally for testing/debugging purposes.

### Test URL
Media retrieval from the backend can be tested by [visiting this
URL](https://deploy-preview-49--storyteller-studio.netlify.app/?mode=viewer&mixamo=remote://m_70b77neq9jsgmy5v31mekzchcbd223.gltf)
in Chrome with the `--disable-web-security` flag (to bypass CORS errors)
2024-02-23 21:05:32 -05:00

114 lines
3.2 KiB
Rust

use bevy::prelude::*;
use space_editor::prelude::*;
pub struct PersistancePlugin;
impl Plugin for PersistancePlugin {
fn build(&self, app: &mut App) {
info!("PersistancePlugin::build");
app.editor_tab_by_trait(
EditorTabName::Other("Persistance".to_string()),
DebugPersistanceTab::default(),
);
#[cfg(feature = "wasm")]
app.add_systems(Update, wasm::forward_save_scene_events)
.add_systems(
OnEnter(SaveState::Idle),
wasm::on_scene_saved.map(bevy::utils::warn),
);
}
}
#[derive(Resource, Default)]
pub struct DebugPersistanceTab {}
impl EditorTab for DebugPersistanceTab {
fn ui(
&mut self,
ui: &mut bevy_inspector_egui::egui::Ui,
commands: &mut Commands,
world: &mut World,
) {
if ui.button("Save").clicked() {
world.send_event(EditorEvent::Save(EditorPrefabPath::MemoryCahce));
}
if ui.button("Load").clicked() {
world.send_event(EditorEvent::Load(EditorPrefabPath::MemoryCahce));
}
if ui.button("Clear level").clicked() {
let mut query = world.query_filtered::<Entity, With<PrefabMarker>>();
for e in query.iter(world) {
commands.entity(e).despawn_recursive();
}
}
}
fn title(&self) -> bevy_inspector_egui::egui::WidgetText {
"Persistance".into()
}
}
#[cfg(feature = "wasm")]
mod wasm {
use bevy::{prelude::*, window::PrimaryWindow};
use space_editor::prelude::{EditorEvent, EditorPrefabPath, PrefabMemoryCache};
use wasm_bindgen::prelude::*;
use crate::wasm::{self, StaticBuffer, WasmMessageBuffer};
static SAVE_SCENE: WasmMessageBuffer<()> = WasmMessageBuffer::new();
#[wasm_bindgen(js_name = saveScene)]
pub fn save_scene() {
SAVE_SCENE.write_message(());
}
pub(super) fn forward_save_scene_events(mut ew_editor_events: EventWriter<EditorEvent>) {
if SAVE_SCENE.take_message().is_some() {
ew_editor_events.send(EditorEvent::Save(EditorPrefabPath::MemoryCahce));
}
}
#[wasm_bindgen(typescript_custom_section)]
const SCENE_SAVED_EVENT: &str = r#"
export interface SceneSavedEvent extends CustomEvent {
type: "scene-saved";
detail: string;
}
declare global {
export interface GlobalEventHandlersEventMap {
"scene-saved": SceneSavedEvent;
}
}
"#;
pub(super) fn on_scene_saved(
r_scene_cache: Res<PrefabMemoryCache>,
r_type_registry: Res<AppTypeRegistry>,
ra_scenes: Res<Assets<DynamicScene>>,
q_window: Query<&Window, With<PrimaryWindow>>,
) -> Result<(), String> {
let handle = r_scene_cache
.scene
.as_ref()
.ok_or_else(|| "Scene cache is empty!".to_string())?;
let serialized = match ra_scenes.get(handle) {
Some(scene) => scene
.serialize_ron(&r_type_registry)
.map_err(|err| format!("{err}")),
None => Err("No scene found for cached handle!".into()),
}?;
let Ok(window) = q_window.get_single() else {
return Ok(());
};
wasm::dispatch_to_js(window, "scene-saved", &serialized)
}
}