2013-12-05
My Little Machines The <machine> directive.

The <machine> directive (implemented in AngularJS) manages the simulation of various one-dimensional (via the $\tau$-axis), non-reversible computations, while enabling the customization of the computation particulars in the document that uses the directive.

In the simple example below, we declare a <machine> that simply tracks its internal state by updating scope variables that are made visible in the HTML.

Example Usage

We’ll use explicit and visible fields to track two classes of computational states:

  1. The machine states and modes of the machine that manage the illusion of time ($\tau$) and its relative rate to the observer. These include the machineTime, machineState and isRunning attributes of the <machine>.

  2. The program states and modes of the particular simulated computation that is reflected not in the <machine>, but in the JavaScript and HTML variables of the document containing the embedded machine. The simulation of this program has its own evolution, rules and state; the underlying <machine> mediates this evolution via the stepFn, and may even inform the simulation via a program rule that observes the underlying machine state. In this example, these program states will all be prefixed by program; e.g., programHistory or programValue.

Hooks into the <machine>

Naming a machine with ptr=

When a <machine> is declared in an HTML file, it is assigned a name via the ptr= attribute of the directive. This name is placed into scope such that it can be used to refer to a particular machine instance. In the example below, we assign the name MA to the machine instance via:

<machine ptr="MA" ...>
  ...
  <button ng-click="MA.resetMachine()">Reset</button>
  ...
</machine>
Program evolution via stepFn
Detect machine reset with resetFn
Detect machine pause with pauseFn
Detect machine halt with haltFn

Reset Step Resume Pause


The source for the above

<div style="background-color: aliceblue;">

<h6>Machine Time: {{ MA.getMachineTime() }}</h6>
<h6>Machine State: {{ MA.getMachineState() }}</h6>
<h6>Machine Running: {{ MA.isRunning() }}</h6>

<h6>Program Value: {{ programValue }}</h6>
<h6>Program History: {{ programHistory }}</h6>

<machine
  ptr="MA"
  ng-init="programHistory = ''; programValue=0;"
  reset-fn="programHistory='-'; programValue=1;"
  step-fn=
    "programHistory = programHistory + 'S'; (MA.getMachineTime() > 10) ? (MA.haltMachine()) : (programValue = programValue * 2)"
  pause-fn="programHistory = programHistory + 'P'"
  resume-fn="programHistory = programHistory + 'R'"
  halt-fn="programHistory = programHistory + 'H'"
  class="mlm background">


<button class="btn btn-default btn-xs" title="Reset" ng-click="MA.resetMachine()">Reset</button>
<button class="btn btn-default btn-xs" title="Step" ng-click="MA.stepMachine()" ng-disabled="!MA.isRunning()">Step</button>
<button class="btn btn-default btn-xs" title="Resume" ng-click="MA.resumeMachine()" ng-disabled="!MA.isRunning() || MA.isPaused()">Resume</span></button>
<button class="btn btn-default btn-xs" title="Pause" ng-click="MA.pauseMachine()" ng-disabled="!MA.isRunning() || !MA.isPaused()">Pause</button>

</machine>
</div>

The source for the <machine> directive


/*
  Module:     pattern.machine
  Directives: <pattern.machine>
*/

(function() {
  'use strict';

  var m = angular.module('pattern.machine', []);

  m.directive('machine', ['$timeout', '$compile', function ($timeout, $compile) {
    return {
      restrict: 'E',
      replace: true,
      transclude: true,

      scope: {
        machine: '=ptr',
        resetFn: '&resetFn',
        haltFn: '&haltFn',
        pauseFn: '&pauseFn',
        resumeFn: '&resumeFn',
        stepFn: '&stepFn'
      },

      template: "<div ng-transclude></div>",

      controller: ['$scope', '$timeout', '$log', function ($scope, $timeout, $log) {
        var machine = {};
        machine.$log = $log;
        machine.machineState = "undefined";
        machine.machineTime = 0;
        machine.machineThread = null;


        machine.resetMachine = function() {
          machine.pauseMachine();
          machine.machineState = "running";
          machine.machineTime = 0;
          $scope.resetFn({});
        };


        machine.haltMachine = function() {
          machine.pauseMachine();
          machine.machineState = "halted";
          machine.machineTime = 0;
          $scope.haltFn({});
        };


        machine.pauseMachine = function() {
          if (machine.machineThread) {
            $timeout.cancel(machine.machineThread);
            machine.machineThread = null;
            $scope.pauseFn({});
          }
        };


        machine.resumeMachine = function() {
          if (machine.machineThread)
          {
            machine.$log.warn("resumeMachine() not reentrant.");
          }
          else if (machine.machineState != "running")
          {
            machine.$log.warn("resumeMachine() requires a runnable machine.");
          }
          else
          {
            var runner = function() {
              if (machine.machineState != "running") {
                $timeout.cancel(machine.machineThread);
                machine.machineThread = null;
              }
              else {
                machine.stepMachine();
                if (machine.machineThread)
                {
                  machine.machineThread = $timeout(runner, 500);
                }
              }
            };

            $scope.resumeFn({});

            machine.machineThread = $timeout(runner, 1000);
          }
        };

        machine.stepMachine = function() {
          if (machine.machineState != "running") {
            machine.$log.warn('stepMachine() requires a running machine');
          }
          else {
            machine.machineTime += 1;
            var result = $scope.stepFn({});
          }
        };


        machine.isRunning = function() {
          return machine.machineState == "running";
        };

        machine.isPaused = function() {
          return machine.machineThread !== null;
        };

        machine.getMachineTime = function() {
          return machine.machineTime;
        };

        machine.getMachineState = function() {
          return machine.machineState;
        };

        $scope.machine = machine;
      }],

      link: function (scope, element, attrs, controller) {
      }
    };
  }]);

})();

Note to Self

How about machine, program, and simulation, where simulation refers to the machine/program composite and its execution. So simulation is a process, but the others are just structures (yeah, processes are just structures over $\tau$, but you know what I mean).