Skip to content

Repository files navigation

🏛️ Effector Model: Harvard Architecture Implementation

A high-performance state management core for models built on Effector. This implementation applies Harvard Architecture principles to reactive business logic, strictly separating Instructions (model definition) from Data (memory instances).


🚀 Key Features

  1. AOT (Ahead-of-Time) Analysis:

    • The entire structure (props, features, modes) is statically analyzed once during definition.
    • The engine generates a flat memory map (integer offsets) and an optimized AccessorClass for instant field retrieval.
  2. Flat Memory Layout:

    • A model instance is a single flat array (Array(totalSize)).
    • All stores, events, and effects—even those deeply nested—are stored in this array.
    • Field access is O(1) via direct array index lookups.
  3. Lifecycle Modes (RAII):

    • Logic acts as a resource. It is "acquired" (mounted) when a mode is entered and "released" (unmounted) when left.
    • Automatic Reset: When re-entering a mode, its state is guaranteed to be reset to defaults. No "stale state" issues.
    • Dead Logic Elimination: Logic for inactive modes physically does not exist in the runtime graph (clearNode is called). We can even throw an error on dead unit access.
  4. Strict Type Safety:

    • Full TypeScript inference for complex fractal structures.
    • Contextual Autocomplete: override({ 'person.$name': ... }) provides Intellisense for string paths.
    • Compile-time collision detection: Prevents naming conflicts between Props, Features, and Modes.

📚 Terminology (The "Device" Metaphor)

Models are treated as complex, composable devices:

  • context (Dependency Injection):
    External dependencies that must be provided during creation (e.g., API clients, global configuration, history objects). Solves "prop drilling".
  • props (Local Memory):
    Basic primitives (define.store, define.event) that belong to this specific model.
  • features (Fractal Composition):
    Nested models embedded within the parent. Physically, their memory is "squashed" into the parent's flat array, but logical access is hierarchical.
  • modes (Operational States):
    Mutually exclusive operational contexts (e.g., Flight vs. Ground).
  • impl (The Circuit Board):
    The wiring function. It receives a Flattened Scope (Props + Features + Modes) and an Environment (Context).

💻 Usage Example

import { createStore, createEffect, sample } from 'effector';
import { model, define, create, redefine, override } from '@effector/model';

// EXTERNAL DEPENDENCIES (Simulated Context)
const loggerFx = createEffect(async (msg: string) => {
  console.log(`[Cockpit Radio]: ${msg}`);
});

// ============================================================================
// 1. STATIC FEATURES (Reusable Components)
// ============================================================================

const PersonFeature = model({
  props: {
    $name: define.store('Anonymous'),
    $wallet: define.store(0),
    employer: define.val('Unknown'),
  },
});

const PilotFeature = model({
  props: {
    $totalFlights: define.store(0),
    incFlights: define.event(),
  },
  impl: (scope) => {
    sample({
      clock: scope.incFlights,
      source: scope.$totalFlights,
      fn: (total) => total + 1,
      target: scope.$totalFlights,
    });
  },
});

// ============================================================================
// 2. DYNAMIC MODES (State Machines)
// ============================================================================

const FlightMode = model({
  props: {
    $altitude: define.store(0),
    land: define.event<void>(), // Landing only possible here
  },
  impl: (scope) => {
    return {
      // Export computed state
      $isAtCruise: scope.$altitude.map((a) => a > 10000),
    };
  },
});

const GroundMode = model({
  props: {
    $fuelLevel: define.store(100),
    refuel: define.event<number>(),
    takeOff: define.event<void>(), // Takeoff only possible from ground
    getPaid: define.event<void>(),
  },
  impl: (scope) => {
    sample({
      clock: scope.refuel,
      source: scope.$fuelLevel,
      fn: (level, add) => Math.min(level + add, 100),
      target: scope.$fuelLevel,
    });
  },
});

// ============================================================================
// 3. MAIN MODEL (The Boeing 747)
// ============================================================================

const BoeingPilot = model({
  // Dependencies required by this model
  context: {
    radioFx: define.effect<string, void>(),
    $flyRate: define.store(500),
  },

  // Local State
  props: {
    $isFlying: define.store(false),
    reportStatus: define.event<void>(),
  },

  // Composition
  features: {
    // Modify definition defaults via redefine()
    person: redefine(
      PersonFeature,
      override({
        employer: define.val('Boeing'),
      }),
    ),
    pilot: PilotFeature,
  },

  // Topology Switching
  modes: {
    operation: {
      source: (scope) => scope.$isFlying,
      // Automatic switching based on source store value
      match: (isFlying) => (isFlying ? 'flight' : 'ground'),
      cases: {
        flight: FlightMode,
        ground: GroundMode,
      },
    },
  },

  // Logic Wiring (Scope + Context)
  impl: (scope, { context }) => {
    // Internal variable (not exposed on API)
    const $pendingPayout = createStore(0);

    // --- Mode Switching Triggers ---

    // Takeoff (only works in Ground Mode)
    sample({
      clock: scope.operation.ground.takeOff,
      fn: () => true,
      target: [scope.$isFlying, scope.pilot.incFlights],
    });

    // Land (only works in Flight Mode)
    sample({
      clock: scope.operation.flight.land,
      fn: () => false,
      target: scope.$isFlying,
    });

    // --- Business Logic ---

    // Accrue pay per flight
    sample({
      clock: scope.pilot.incFlights,
      source: { current: $pendingPayout, rate: context.$flyRate },
      fn: ({ current, rate }) => current + rate,
      target: $pendingPayout,
    });

    // Payout (Only possible on Ground)
    sample({
      clock: scope.operation.ground.getPaid,
      source: { wallet: scope.person.$wallet, accrued: $pendingPayout },
      fn: ({ wallet, accrued }) => wallet + accrued,
      target: scope.person.$wallet,
    });

    // Reset internal debt counter after payout
    sample({
      clock: scope.operation.ground.getPaid,
      target: ($pendingPayout as any).reinit,
    });

    // --- Reporting (Using Injected Context) ---

    sample({
      clock: scope.reportStatus,
      source: {
        isFlying: scope.$isFlying,
        name: scope.person.$name,
        payout: $pendingPayout,
        // Safe access to mode state: returns default value if mode inactive
        altitude: scope.operation.flight.$altitude,
        fuelLevel: scope.operation.ground.$fuelLevel,
      },
      fn: (state) => {
        const status = state.isFlying ? `In Flight at ${state.altitude}m` : `On Ground (Fuel: ${state.fuelLevel}%)`;
        return `Report for ${state.name} (${scope.person.employer}): ${status}. Pending Pay: ${state.payout}$`;
      },
      target: context.radioFx,
    });

    return {
      $pendingPayout,
    };
  },
});

// ============================================================================
// 4. INSTANTIATION (Runtime)
// ============================================================================

const captainPilot = create({
  model: BoeingPilot,

  // 1. Inject Context (DI)
  context: {
    radioFx: loggerFx,
    $flyRate: createStore(1500), // Override default rate
  },

  // 2. Override Props (Path-based, Typesafe)
  props: override({
    'person.$name': createStore('Captain Maverick'),
    // Note: No 'features.' prefix needed, path matches the scope structure
  }),
});

// --- SCENARIO ---

// 1. On Ground. Check defaults.
captainPilot.reportStatus();
// > [Cockpit Radio]: Report for Captain Maverick (Boeing): On Ground (Fuel: 100%). Pending Pay: 0$

// 2. Refuel (Works)
captainPilot.operation.ground.refuel(-20);

// 3. Takeoff
captainPilot.operation.ground.takeOff();

// 4. Airborne behavior
// Try to refuel? Ignored. Ground logic is unmounted.
captainPilot.operation.ground.refuel(50);

// Update flight state (Typescript prevents access to .state directly, must use events or internal logic)
// For demo, we simulate internal update:
// ... (altitude changes to 12000) ...

captainPilot.reportStatus();
// > [Cockpit Radio]: Report for Captain Maverick (Boeing): In Flight at 12000m. Pending Pay: 1500$

// 5. Land
captainPilot.operation.flight.land();

// 6. Get Paid
captainPilot.operation.ground.getPaid();

About

EFFECTOR MODEL: HARVARD ARCHITECTURE. Architecture separating Model Definition (Instructions) and State (Data). AOT analysis compiles structure into a flat memory array for O(1) access. Features fractal composition (features), dynamic logic lifecycles (modes), and typed DI via string paths.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Sponsor this project

Packages

Contributors

Languages