Build A Zero-Bloat Demo Studio For State Machines
Hey guys! Ever wanted to show off the power of state machines in a visually compelling way, without bogging down your production code? Let's dive into creating a Demo Studio – a system built to demonstrate state machine concepts with animated examples, all while keeping your main project lean and mean. This guide will walk you through building a flexible, zero-bloat system that lets you showcase different versions of your actors, customize the environment, and even integrate with tools like Stately Inspector. Ready to get started?
The Need for Speed (and Isolation)
Primary: Conference Talk Demonstrations
So, why build a Demo Studio in the first place? Well, imagine you're giving a talk on state machines at a conference. You want to visually demonstrate how these machines work, showing off patterns with animated examples. You want to make it super clear and bring your code to life. You also want to show progressive complexity, meaning, how your machine evolves from simple to complex versions. Think of it like a chef actor in your game. In the first version, maybe he just moves around. In the next, he can pick up ingredients. And in the final, production-ready version, he's a culinary master! Furthermore, you'll need to isolate these actors (chef, hen, egg) in their own little worlds to make things clear. No need to bring in the entire game environment. And, if you're a real pro, you'll need to sync up these demos with Stately Inspector, the tool that lets you see the states and transitions in real-time. This helps you explain the behavior of your state machine and it's easy to show to everyone in the audience.
Key Requirement: Zero Production Bloat
Here's the kicker, the demos should be completely excluded from your production builds. You don't want any demo code to accidentally make its way into the live version of your game. It's like having a secret lab where you can experiment without affecting the real world. This setup achieves branch-like isolation, without the hassle of actual branches. All your demo versions can live in the same codebase/branch, making it easy to manage. And, if you are really wanting to showcase the demos, you can also optionally deploy the demos separately.
Architectural Blueprint
1. Versioned State Machines AND Components
We'll build evolutionary versions of both state machines and the React components that represent them. Here's a quick look at how the file structure might shake out:
src/
demo-studio/ # ← Excluded from production builds
machines/
chef/
chef-v1-basic.machine.ts # Simple movement
chef-v2-physics.machine.ts # + Acceleration
chef-v3-full.machine.ts # Production machine
hen/
hen-v1-simple.machine.ts # Basic movement
hen-v2-laying.machine.ts # + Egg laying
hen-v3-pausing.machine.ts # + Pause behavior
hen-v4-full.machine.ts # Production version
egg/
egg-v1-falling.machine.ts # Just falling
egg-v2-landing.machine.ts # + Landing
egg-v3-hatching.machine.ts # + Hatching
egg-v4-full.machine.ts # Production version
components/ # Versioned components match machines
chef/
Chef-v1-basic.tsx # Simpler rendering
Chef-v2-physics.tsx # More sprite frames
Chef-v3-full.tsx # Full rendering
hen/
Hen-v1-simple.tsx # Example: Walking only
Hen-v2-laying.tsx # Example: + Laying animation
Hen-v3-full.tsx # Full sprite set
egg/
Egg-v1-falling.tsx # Example: Falling only
Egg-v2-hatching.tsx # Example: + Hatching
Egg-v3-full.tsx # All transformations
Why version components too, you ask? It's all about making your demos super clear and easy to follow. We can match rendering complexity to state machine complexity. You can use different sprite subsets per version. It really helps to demonstrate the "component follows machine state" concept. It's like having true version isolation, like separate branches, but without the extra hassle.
2. Config-Based Demo Creation
Each demo is defined by a configuration object. This gives you flexibility and control. Here's an example:
const demoConfigs = {
'example-demo-id': {
actors: [{
type: 'hen', // Actor type
machineVersion: 'v1-simple', // State machine version
componentVersion: 'v1-simple', // React component version
startPosition: { x: 200, y: 150 }
}],
background: { type: 'solid', color: '#f0f0f0' },
inspector: { visible: true, position: 'right' },
title: 'Example Demo Title'
}
};
This setup allows you to mix any actor types (chef, hen, egg, chick). You can mix any machine/component versions and have multiple actors in one demo. It gives you the flexibility to showcase different versions of the actor and show what they're capable of. Moreover, you can also have version combinations across actors.
3. Build-Time Exclusion Strategy
This is where the magic of zero-bloat happens. We'll use Vite (or your preferred build tool) to exclude the demo code from production builds. Here's how to set it up:
Vite Configuration: Add this to your vite.config.ts:
// vite.config.ts
export default defineConfig({
build: {
rollupOptions: {
external: (id) => {
// Exclude demo-studio in production builds
if (process.env.VITE_DEMO_MODE !== 'true') {
return id.includes('/demo-studio/');
}
return false;
}
}
}
});
Conditional Routing: In App.tsx, make sure your demo route only loads in dev or when the demo mode is enabled:
// App.tsx
const routes = [
{ path: '/', element: <Game /> },
// Only load demo route in dev or VITE_DEMO_MODE
...(import.meta.env.DEV || import.meta.env.VITE_DEMO_MODE
? [{ path: '/demo/:id', element: lazy(() => import('./demo-studio/DemoStudio')) }]
: []
)
];
Build Scripts: Update your package.json with these scripts:
{
"scripts": {
"build": "vite build", // Production (no demos)
"build:with-demos": "VITE_DEMO_MODE=true vite build", // Includes demos
"dev": "vite" // Dev (always has demos)
}
}
The Result: Now, when you run pnpm build, the demo code will be excluded. Run pnpm build:with-demos to build a version that includes demos for a separate deployment. The development environment will always have demos at /demo/:id.
4. ActorFactory with Version Loading
To make this all work, we'll create an ActorFactory. This factory dynamically loads the versioned state machine and the corresponding component. It's like having a magic box that creates the right actor for your demo. Here is a sample code:
async function createDemoActor(config: ActorConfig) {
// Dynamically load versioned machine
const machineModule = await import(
`./machines/${config.type}/${config.type}-${config.machineVersion}.machine.ts`
);
// Dynamically load versioned component
const componentModule = await import(
`./components/${config.type}/${config.type}-${config.componentVersion}.tsx`
);
return {
actor: spawn(machineModule.machine, { input: config.input }),
Component: componentModule.default
};
}
Features to Look Forward To
Core Capabilities
Here are some core features your Demo Studio will offer:
- Mix-and-match actors: Combine any game actors you want in a single demo.
- Mix-and-match versions: Showcase different versions of your state machines and components.
- Custom backgrounds: Use a variety of backgrounds – none, solid colors, gradients, images, or even the game background.
- Stately Inspector: Integrate Stately Inspector for real-time state visualization.
- Dynamic controls: Auto-generate controls based on your demo configuration.
- Zero bloat: Make sure the demo code is completely excluded from production builds.
Background Options
To make your demos visually appealing, you can use these background options:
none: Transparent (for overlaying on slides).solid: Single color.gradient: Visual interest without game assets.image: Custom background images.game: Use the actual game background.
Control Panel (Auto-Generated)
An auto-generated control panel will be created:
- Actor-specific controls (e.g., arrow keys for the chef).
- Spawn buttons for dynamic actors.
- Manual event triggers.
- Visualization toggles (collision zones, etc.).
- Playback controls (reset, slow motion, speed).
- Recording-optimized layout.
Demo Ideas to Spark Inspiration
Here are some demo examples:
Example: Evolutionary Progression
Show versions of the same actor to highlight added complexity. Side-by-side comparisons of different versions can be created. New states/transitions in each version can be highlighted.
Example: Actor Communication
Create demos with multiple actors showing parent-child events. Collision detection visualization can be added. You can create event flows between actors.
Example: State-Driven Rendering
Utilize the same machine with different rendering approaches. Demonstrate a component reacting to state changes.
Implementation Steps: Get Your Hands Dirty
Infrastructure
- Configure Vite for conditional demo exclusion.
- Add the
build:with-demosscript. - Create the
/demo-studiodirectory structure. - Build the demo route at
/demo/:demoId. - Implement the
ActorFactorywith version loading. - Test the production build to exclude demo code (bundle analysis).
Core Systems
- Create
BackgroundRenderer(all background types). - Integrate
@stately/inspectwith multi-actor support. - Build
ControlPanelwith dynamic controls. - Create
DemoSelectorUI for browsing demos. - Implement
ConfigEditorfor live config editing. - Add demo persistence (localStorage or file-based).
Versioning System
- Define a versioning strategy for machines (how many versions, what progression).
- Define a versioning strategy for components (match machines or separate?).
- Create initial versioned machines (TBD which actors/versions).
- Create initial versioned components (TBD which actors/versions).
Demo Configurations
- Determine which demos to create based on talk narrative.
- Implement demo configs.
- Test each demo configuration.
Polish
- Export demo as standalone HTML.
- Recording mode (optimized layout for screen capture).
- URL-based sharing with query params.
- Keyboard shortcuts for common actions.
- Documentation for creating new demos.
- Documentation for deployment strategy.
File Structure: Your Project Layout
src/
demo-studio/ # ← EXCLUDED from production
machines/ # Versioned state machines
chef/
[versioned machine files]
hen/
[versioned machine files]
egg/
[versioned machine files]
components/ # Versioned React components
chef/
[versioned component files]
hen/
[versioned component files]
egg/
[versioned component files]
DemoStudio.tsx # Main playground component
DemoCanvas.tsx # Konva stage wrapper
ActorFactory.ts # Load machines + components by version
BackgroundRenderer.tsx # Render backgrounds from config
ControlPanel.tsx # Dynamic controls
InspectorIntegration.tsx # Stately inspector integration
DemoSelector.tsx # Browse/switch demos
ConfigEditor.tsx # Live config editing
demo-configs.ts # All demo definitions
types.ts # TypeScript interfaces
Chef/ # ← Production (always included)
chef.machine.ts # Production machine
Chef.tsx # Production component
Hen/ # ← Production
Egg/ # ← Production
... (rest of game)
Deployment Strategy: Where Your Demos Live
- Production:
yourgame.com- Normal build, zero demo code. - Demo Site:
demos.yourgame.com- Demo build deployed separately. - Development: Demos always available at
/demo/:demoId.
Success: The Finish Line
Here are the goals for a successful build:
- ✅ Production build contains zero demo code (verified via bundle analysis).
- ✅ You can create new demos by adding config objects only (no code changes).
- ✅ You can show multiple versions of the same actor.
- ✅ Stately Inspector syncs with visual output.
- ✅ Mix any combination of actor versions.
- ✅ Custom background per demo.
- ✅ Shareable demo URLs.
- ✅ Recording-ready output for screen capture.
Wrapping Up: Reap the Benefits
By following these steps, you will enjoy:
- True branch-like isolation without branch overhead.
- Educational machine + component evolution demonstrations.
- Zero production bloat.
- All versions in the same codebase.
- Optional separate demo deployment.
- Reusable for future talks, documentation, and experimentation.
That's it, guys! You now have the knowledge and tools to create your very own Demo Studio. It's not just a way to show off your state machines; it's a testament to your commitment to clear, engaging communication. Get building, and happy coding! Do you have questions? Let me know, and I'll do my best to help!"