Skip to content

5-Minute Getting Started

Overview

This guide takes you from zero to a working App Engine in 5 minutes. By the end, you'll have: - Created an AppEngine - Registered and executed a command - Created and executed a task - Published and subscribed to events - Loaded a resource - Done undo/redo - Shut down cleanly


Step 1: Create the Engine

use app_shell::app_engine::{AppEngine, Bootstrap, Lifecycle};

let mut engine: AppEngine = Bootstrap::create().expect("bootstrap failed");

That's it. All subsystems are wired and ready.


Step 2: Register a Command

Define a handler, register it:

use app_shell::app_engine::*;
use std::any::Any;

// Your domain input type
#[derive(Debug, Clone)]
struct GreetInput {
    name: String,
}

// Your command handler
struct GreetCommand;

impl CommandHandler for GreetCommand {
    fn execute(
        &self,
        input: &CommandInput,
        _context: &CommandContext,
        _engine: &EngineRef,
    ) -> Result<CommandResult, CommandError> {
        let input: &GreetInput = input.get::<GreetInput>()
            .ok_or_else(|| CommandError::InvalidArguments("expected GreetInput".to_string()))?;

        Ok(CommandResult::success_with(format!("Hello, {}!", input.name)))
    }
}

// Register
engine.command_executor_mut().register(
    CommandDefinition::new("app.greet", "Greet", "Greet someone by name"),
    Box::new(GreetCommand),
)?;

Step 3: Execute the Command

// Start the engine
engine.initialize()?;
engine.start()?;

// Create and execute the command
let cmd = Command::new(
    "app.greet",
    CommandContext::new(ExecutionId::new()),
    CommandInput::new(GreetInput { name: "World".to_string() }),
);

let result = engine.execute_command(&cmd)?;

assert!(result.is_success());
assert_eq!(result.output::<String>(), Some(&"Hello, World!".to_string()));

Step 4: Register and Execute a Task

For longer operations with lifecycle:

// Your task handler
struct CountTask;

impl TaskHandler for CountTask {
    fn execute(
        &self,
        _input: &TaskInput,
        ctx: &TaskContext,
        _engine: &EngineRef,
    ) -> Result<TaskResult, TaskError> {
        // Check cancellation
        if ctx.is_cancelled() {
            return Ok(TaskResult::cancelled());
        }

        // Report progress (emits signal)
        ctx.output().progress(0.5);

        // Do work
        let count = 42;

        ctx.output().progress(1.0);

        Ok(TaskResult::completed_with(count))
    }
}

// Register
engine.task_manager_mut().register(
    TaskDefinition::new("app.count", "Count", "Count to 42")
        .with_cancel_support(),
    Box::new(CountTask),
)?;

// Create and execute
let task_id = engine.task_manager_mut().create(
    "app.count",
    TaskContext::new(ExecutionId::new()),
    TaskInput::empty(),
)?;

let result = engine.start_task(task_id)?;
assert!(result.is_completed());
assert_eq!(result.output::<i32>(), Some(&42));

Step 5: Publish and Subscribe to Events

use std::sync::{Arc, Mutex};

let received = Arc::new(Mutex::new(false));
let r = received.clone();

// Subscribe BEFORE publishing
engine.event_bus_mut().subscribe(
    "task.completed",
    event_handler(move |_event: &Event| {
        *r.lock().unwrap() = true;
    }),
);

// Publish (from a handler or directly)
engine.event_bus().publish(
    &Event::new("task.completed").with_source("QuickStart")
);

assert!(*received.lock().unwrap());

Closure Wrapper for Events

If you want to use closures (not just EventHandler impl):

struct ClosureEventHandler<F: Fn(&Event) + Send + Sync + 'static> { f: F }
impl<F: Fn(&Event) + Send + Sync + 'static> EventHandler for ClosureEventHandler<F> {
    fn handle(&self, event: &Event) { (self.f)(event); }
}
fn event_handler<F: Fn(&Event) + Send + Sync + 'static>(f: F) -> Box<dyn EventHandler> {
    Box::new(ClosureEventHandler { f })
}

// Usage:
engine.event_bus_mut().subscribe("my.event", event_handler(|event| {
    println!("Got: {}", event.event_type());
}));

Step 6: Load a Resource

// Define a resource loader
struct TextLoader;
impl ResourceLoader for TextLoader {
    fn resource_type(&self) -> &str { "text" }
    fn load(&self, source: &str) -> Result<Box<dyn Any + Send>, ResourceError> {
        Ok(Box::new(format!("content:{}", source)))
    }
}

// Register the loader
engine.resource_manager().register_loader(Box::new(TextLoader));

// Load (RAII handle auto-releases on drop)
let handle = engine.resource_manager().load("text", "file:///welcome.txt")?;

// Access the data
let data = engine.resource_manager()
    .with_resource(handle.resource_id(), |r| {
        r.data::<String>().map(|s| s.clone())
    })
    .flatten();

assert_eq!(data.as_deref(), Some("content:file:///welcome.txt"));
// Handle drops here → ref count = 0, resource stays in cache

Step 7: Undo / Redo

// Record a history entry
engine.history_store().append(
    HistoryEntry::new("object.create")
        .undoable()
        .with_data("rect_1".to_string())
        .with_source("QuickStart")
);

engine.history_store().append(
    HistoryEntry::new("object.move")
        .undoable()
        .with_data("moved".to_string())
        .with_inverse("unmoved".to_string())
);

assert_eq!(engine.history_store().active_count(), 2);

// Undo
let undo_result = engine.history_store().undo();
assert_eq!(undo_result.count(), 1);
assert_eq!(engine.history_store().position(), 1);

// Redo
let redo_result = engine.history_store().redo();
assert_eq!(redo_result.count(), 1);
assert_eq!(engine.history_store().position(), 2);

Step 8: Shut Down Cleanly

// Stop — cancels all running tasks
engine.stop()?;

// Dispose — unloads all non-in-use resources
engine.dispose()?;

assert_eq!(engine.state(), RuntimeState::Disposed);

Complete Example

use app_shell::app_engine::*;
use std::any::Any;
use std::sync::{Arc, Mutex};

// --- Stubs ---

struct GreetCommand;

impl CommandHandler for GreetCommand {
    fn execute(&self, input: &CommandInput, _: &CommandContext, _: &EngineRef) -> Result<CommandResult, CommandError> {
        let name: &String = input.get::<String>()
            .ok_or_else(|| CommandError::InvalidArguments("expected name".to_string()))?;
        Ok(CommandResult::success_with(format!("Hello, {}!", name)))
    }
}

struct TextLoader;
impl ResourceLoader for TextLoader {
    fn resource_type(&self) -> &str { "text" }
    fn load(&self, source: &str) -> Result<Box<dyn Any + Send>, ResourceError> {
        Ok(Box::new(format!("content:{}", source)))
    }
}

// --- Closure wrapper for events ---

struct ClosureEventHandler<F: Fn(&Event) + Send + Sync + 'static> { f: F }
impl<F: Fn(&Event) + Send + Sync + 'static> EventHandler for ClosureEventHandler<F> {
    fn handle(&self, event: &Event) { (self.f)(event); }
}
fn event_handler<F: Fn(&Event) + Send + Sync + 'static>(f: F) -> Box<dyn EventHandler> {
    Box::new(ClosureEventHandler { f })
}

// --- Main ---

fn main() {
    // 1. Create the engine
    let mut engine: AppEngine = Bootstrap::create().expect("bootstrap failed");

    // 2. Register a command
    engine.command_executor_mut().register(
        CommandDefinition::new("app.greet", "Greet", "Greet someone"),
        Box::new(GreetCommand),
    ).unwrap();

    // 3. Register a resource loader
    engine.resource_manager().register_loader(Box::new(TextLoader));

    // 4. Subscribe to events
    let received = Arc::new(Mutex::new(false));
    let r = received.clone();
    engine.event_bus_mut().subscribe(
        "greet.done",
        event_handler(move |_event: &Event| { *r.lock().unwrap() = true; }),
    );

    // 5. Start the engine
    engine.initialize().unwrap();
    engine.start().unwrap();

    // 6. Execute a command
    let cmd = Command::new(
        "app.greet",
        CommandContext::new(ExecutionId::new()),
        CommandInput::new("World".to_string()),
    );
    let result = engine.execute_command(&cmd).unwrap();
    println!("{}", result.output::<String>().unwrap());

    // 7. Publish an event
    engine.event_bus().publish(&Event::new("greet.done").with_source("main"));
    assert!(*received.lock().unwrap());

    // 8. Load a resource (RAII handle auto-releases on drop)
    let handle = engine.resource_manager()
        .load("text", "file:///welcome.txt").unwrap();
    let data = engine.resource_manager()
        .with_resource(handle.resource_id(), |r| r.data::<String>().map(|s| s.clone()))
        .flatten();
    println!("Loaded: {}", data.unwrap());

    // 9. Undo/redo
    engine.history_store().append(
        HistoryEntry::new("greet").undoable().with_source("main"),
    );
    engine.history_store().undo();
    engine.history_store().redo();

    // 10. Shut down (cancels tasks, unloads resources)
    engine.stop().unwrap();
    engine.dispose().unwrap();

    println!("Done! State: {:?}", engine.state());
}

Output:

Hello, World!
Loaded: content:file:///welcome.txt
Done! State: Disposed


Quick Reference Card

// Create
let mut engine = Bootstrap::create()?;

// Register command
engine.command_executor_mut().register(def, handler)?;

// Register task
engine.task_manager_mut().register(def, handler)?;

// Register resource loader
engine.resource_manager().register_loader(loader);

// Subscribe to event
engine.event_bus_mut().subscribe("type", handler);

// Lifecycle
engine.initialize()?;
engine.start()?;
engine.run()?;
engine.stop()?;
engine.dispose()?;

// Execute command
let result = engine.execute_command(&cmd)?;

// Execute task
let task_id = engine.task_manager_mut().create(def_id, ctx, input)?;
let result = engine.start_task(task_id)?;

// Publish event
engine.event_bus().publish(&Event::new("type"));

// Load resource
let handle = engine.resource_manager().load("type", "source")?;

// Undo/redo
engine.history_store().undo();
engine.history_store().redo();

Next Steps