Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion desktop/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ impl App {
let wake = Arc::new(move || {
wake_scheduler.schedule(AppEvent::DesktopWrapperMessage(DesktopWrapperMessage::Wake));
});
let desktop_wrapper = DesktopWrapper::new(rand::rng().random(), Arc::new(resource_storage), wgpu_context.clone(), wake);
let desktop_wrapper = DesktopWrapper::new(rand::rng().random(), Arc::new(resource_storage), dirs::app_autosave_documents_dir(), wgpu_context.clone(), wake);

Self {
render_state: None,
Expand Down
4 changes: 2 additions & 2 deletions desktop/wrapper/src/intercept_frontend_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ pub(super) fn intercept_frontend_message(dispatcher: &mut DesktopWrapperMessageD
default_filename: name,
default_folder: folder,
filters: vec![FileFilter {
name: "Graphite".to_string(),
extensions: vec!["graphite".to_string()],
name: "Graphite Document".to_string(),
extensions: vec!["gdd".to_string()],
}],
context: SaveFileDialogContext::Document { document_id, content },
});
Expand Down
4 changes: 2 additions & 2 deletions desktop/wrapper/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub struct DesktopWrapper {
}

impl DesktopWrapper {
pub fn new(uuid_random_seed: u64, resource_storage: Arc<dyn ResourceStorage>, wgpu_context: WgpuContext, schedule_wake: Wake) -> Self {
pub fn new(uuid_random_seed: u64, resource_storage: Arc<dyn ResourceStorage>, working_copy_root: std::path::PathBuf, wgpu_context: WgpuContext, schedule_wake: Wake) -> Self {
#[cfg(target_os = "windows")]
let host = Host::Windows;
#[cfg(target_os = "macos")]
Expand All @@ -36,7 +36,7 @@ impl DesktopWrapper {
let application_io = PlatformApplicationIo::new_with_context(wgpu_context);

Self {
editor: Editor::new(env, uuid_random_seed, resource_storage, application_io, schedule_wake),
editor: Editor::new(env, uuid_random_seed, resource_storage, Some(working_copy_root), application_io, schedule_wake),
}
}

Expand Down
3 changes: 3 additions & 0 deletions editor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ gpu = ["interpreted-executor/gpu", "dep:wgpu-executor"]
# Local dependencies
graphite-proc-macros = { workspace = true }
graph-craft = { workspace = true }
graph-storage = { workspace = true, features = ["conversion"] }
document-format = { workspace = true }
document-container = { workspace = true }
graphene-hash = { workspace = true }
interpreted-executor = { workspace = true }
graphene-std = { workspace = true } # NOTE: `core-types` should not be added here because `graphene-std` re-exports its contents
Expand Down
11 changes: 9 additions & 2 deletions editor/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,18 @@ pub struct Editor {
}

impl Editor {
pub fn new(environment: Environment, uuid_random_seed: u64, resource_storage: Arc<dyn ResourceStorage>, mut application_io: PlatformApplicationIo, wake: Wake) -> Self {
pub fn new(
environment: Environment,
uuid_random_seed: u64,
resource_storage: Arc<dyn ResourceStorage>,
working_copy_root: Option<std::path::PathBuf>,
mut application_io: PlatformApplicationIo,
wake: Wake,
) -> Self {
ENVIRONMENT.set(environment).expect("Editor shoud only be initialized once");
graphene_std::uuid::set_uuid_seed(uuid_random_seed);

let mut dispatcher = Dispatcher::new(resource_storage);
let mut dispatcher = Dispatcher::new(resource_storage, working_copy_root);
dispatcher.message_handlers.future_message_handler.set_wake(wake);
application_io.inject_resource_proxy(dispatcher.message_handlers.resource_storage_message_handler.resources());
crate::node_graph_executor::replace_application_io(application_io);
Expand Down
3 changes: 3 additions & 0 deletions editor/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@ pub const COLOR_OVERLAY_BLACK_75: &str = "#000000bf";

// DOCUMENT
pub const FILE_EXTENSION: &str = "graphite";
/// New document container format. Save now writes a `.gdd` (with the legacy `.graphite` embedded as a
/// fallback during the dual-write soak); `.graphite` stays a supported open/import input.
pub const GDD_FILE_EXTENSION: &str = "gdd";
pub const DEFAULT_DOCUMENT_NAME: &str = "Untitled Document";
pub const MAX_UNDO_HISTORY_LEN: usize = 100; // TODO: Add this to user preferences
pub const AUTO_SAVE_TIMEOUT_SECONDS: u64 = 1;
Expand Down
3 changes: 2 additions & 1 deletion editor/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,10 @@ const DEBUG_MESSAGE_BLOCK_LIST: &[MessageDiscriminant] = &[
const DEBUG_MESSAGE_ENDING_BLOCK_LIST: &[&str] = &["PointerMove", "PointerOutsideViewport", "Overlays", "Draw", "CurrentTime", "Time"];

impl Dispatcher {
pub fn new(resource_storage: Arc<dyn ResourceStorage>) -> Self {
pub fn new(resource_storage: Arc<dyn ResourceStorage>, working_copy_root: Option<std::path::PathBuf>) -> Self {
let mut s = Self::default();
s.message_handlers.resource_storage_message_handler = ResourceStorageMessageHandler::new(resource_storage);
s.message_handlers.portfolio_message_handler.set_working_copy_root(working_copy_root);
s
}

Expand Down
45 changes: 20 additions & 25 deletions editor/src/messages/future/future_message_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ use futures::channel::mpsc::{UnboundedReceiver, UnboundedSender, unbounded};

use crate::messages::prelude::*;

// Native spawns onto a multi-thread tokio runtime, so the boxed future must be `Send`. Wasm uses
// `spawn_local` on the single JS thread, where `Send` is unavailable (OPFS/`JsFuture` are `!Send`)
// and unnecessary. `WasmNotSend` (`Send` on native, no-op on wasm) expresses the input bound on
// `MessageFuture::new`; the stored trait-object alias still needs a `cfg` split because `Send` is
// an auto trait usable in a `dyn` bound while `WasmNotSend` is not.
#[cfg(not(target_family = "wasm"))]
type InnerMessageFuture = Pin<Box<dyn Future<Output = Message> + Send + 'static>>;
#[cfg(target_family = "wasm")]
Expand Down Expand Up @@ -132,45 +137,35 @@ impl MessageHandler<FutureMessage, FutureMessageContext> for FutureMessageHandle

#[cfg(not(target_family = "wasm"))]
fn default_spawner() -> Arc<dyn MessageSpawner> {
Arc::new(TokioSpawner::default())
Arc::new(TokioSpawner)
}

#[cfg(target_family = "wasm")]
fn default_spawner() -> Arc<dyn MessageSpawner> {
Arc::new(WasmSpawner)
}

/// Process-global runtime for editor async work. Held in a `LazyLock` so it lives for the lifetime
/// of the process and is never dropped: dropping a `tokio::runtime::Runtime` blocks to join its
/// worker threads, which panics if it happens inside an async context (e.g. a `#[tokio::test]` body
/// or the desktop event loop). A leaked-for-process runtime sidesteps that entirely.
#[cfg(not(target_family = "wasm"))]
struct TokioSpawner {
/// Built lazily on first spawn. `multi_thread(1)` lets Tokio manage its own driver.
runtime: std::sync::OnceLock<tokio::runtime::Runtime>,
}

#[cfg(not(target_family = "wasm"))]
impl Default for TokioSpawner {
fn default() -> Self {
Self { runtime: std::sync::OnceLock::new() }
}
}
static EDITOR_ASYNC_RUNTIME: std::sync::LazyLock<tokio::runtime::Runtime> = std::sync::LazyLock::new(|| {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.thread_name("graphite-async")
.enable_all()
.build()
.expect("failed to construct async-message tokio runtime")
});

#[cfg(not(target_family = "wasm"))]
impl TokioSpawner {
fn runtime(&self) -> &tokio::runtime::Runtime {
self.runtime.get_or_init(|| {
tokio::runtime::Builder::new_multi_thread()
.worker_threads(1)
.thread_name("graphite-async")
.enable_all()
.build()
.expect("failed to construct async-message tokio runtime")
})
}
}
struct TokioSpawner;

#[cfg(not(target_family = "wasm"))]
impl MessageSpawner for TokioSpawner {
fn spawn(&self, future: InnerMessageFuture, results: UnboundedSender<Message>, wake: Wake) {
self.runtime().spawn(async move {
EDITOR_ASYNC_RUNTIME.spawn(async move {
let message = future.await;
let _ = results.unbounded_send(message);
wake();
Expand Down
Loading
Loading