WaBis

walter.bislins.ch

Datei: Sim: Sim.js

Inhalt der Datei: ./javascript/Sim/src/Sim.js
// Sim.js (C) 2017; Walter Bislin
//
// Licence: Feel free to use and change this file in any way you want for your projects.
// Do not sell this file as it is to make profit of not your own work please.
//
// This class controls real time animations and provides some statistics.
// For documentation see http://walter.bislins.ch/doc/sim

function Sim( params ) {

  this.TimeStep         = xDefNum( params.TimeStep, 0.001 ); // s
  this.TimeSpeed        = xDefNum( params.TimeSpeed, 1 );
  this.TargetFps        = xDefNum( params.TargetFps, 25 );
  this.SimObj           = xDefObj( params.SimObj, null );

  this.DeltaTime        = 0;  // [s] computed from TimeStep and current delta frame time, limited by TargetFps

  this.TimeStepFuncs    = new xCallbackChain();
  this.FrameFuncs       = new xCallbackChain();
  this.ResetFuncs       = new xCallbackChain();
  this.RunFuncs         = new xCallbackChain();
  this.StopFuncs        = new xCallbackChain();

  this.SimulTime        = 0;  // s
  this.RealTime         = 0;  // s
  this.Running          = false;

  // statistics
  this.EnableStatistics = xDefBool( params.EnableStatistics, false );
  this.NAvgSamples      = xDefNum( params.NAvgSamples, 25 );
  this.SampleTime       = xDefNum( params.SampleTime, 500 ); // ms

  this.CpuLoadSim       = 0;  // 0..1
  this.CpuLoadSimMax    = 0;  // 0..1
  this.CpuLoadSimAvg    = 0;  // 0..1

  this.CpuLoadDraw      = 0;  // 0..1
  this.CpuLoadDrawMax   = 0;  // 0..1
  this.CpuLoadDrawAvg   = 0;  // 0..1

  this.CpuLoadFrame     = 0;  // 0..1
  this.CpuLoadFrameMax  = 0;  // 0..1
  this.CpuLoadFrameAvg  = 0;  // 0..1

  this.Fps              = 0;  // average FPS over NSvgSamples
  this.FpsMin           = 0;
  this.FpsMax           = 0;

  this.NFrames          = 0;

  // private properties
  this.StartTime           = 0;  // ms
  this.LastFrameTime       = 0;  // ms
  this.LastSampleTime      = 0;  // ms
  this.IsInAnimationFrame  = false;
  this.AnimationFrame      = null;
  this.CurrCpuLoadSimMax   = 0;
  this.CurrCpuLoadFrameMax = 0;
  this.CurrFpsMax          = 0;
  this.CurrFpsMin          = 1e9;

  if (params.TimeStepFuncs) this.AddTimeStepFunc( params.TimeStepFuncs );
  if (params.FrameFuncs)    this.AddFrameFunc( params.FrameFuncs );
  if (params.ResetFuncs)    this.AddResetFunc( params.ResetFuncs );
  if (params.RunFuncs)      this.AddRunFunc( params.RunFuncs );
  if (params.StopFuncs)     this.AddStopFunc( params.StopFuncs );
}

Sim.prototype.AddTimeStepFunc = function( funcs ) {
  if (xArray(funcs)) {
    xArrForEach(
      funcs,
      function CB_AddTimeStepFunc(func){
        this.AddTimeStepFunc(func);
      },
      this
    );
  } else {
    this.TimeStepFuncs.Add( funcs );
  }
}

Sim.prototype.AddFrameFunc = function( funcs ) {
  if (xArray(funcs)) {
    xArrForEach(
      funcs,
      function CB_AddFrameFunc(func){
        this.AddFrameFunc(func);
      },
      this
    );
  } else {
    this.FrameFuncs.Add( funcs );
  }
}

Sim.prototype.AddResetFunc = function( funcs ) {
  if (xArray(funcs)) {
    xArrForEach(
      funcs,
      function CB_AddResetFunc(func){
        this.AddResetFunc(func);
      },
      this
    );
  } else {
    this.ResetFuncs.Add( funcs );
  }
}

Sim.prototype.AddRunFunc = function( funcs ) {
  if (xArray(funcs)) {
    xArrForEach(
      funcs,
      function CB_AddRunFunc(func){
        this.AddRunFunc(func);
      },
      this
    );
  } else {
    this.FrameRun.Add( funcs );
  }
}

Sim.prototype.AddStopFunc = function( funcs ) {
  if (xArray(funcs)) {
    xArrForEach(
      funcs,
      function CB_AddStopFunc(func){
        this.AddStopFunc(func);
      },
      this
    );
  } else {
    this.StopFuncs.Add( funcs );
  }
}

Sim.prototype.Reset = function() {
  if (this.Running) {
    this.Stop();
  }
  this.RealTime = 0;
  this.SimulTime = 0;
  this.StartTime = 0;
  this.LastFrameTime = 0;
  this.LastSampleTime = 0;
  this.NFrames = 0;

  if (this.EnableStatistics) {
    this.CpuLoadSim = 0;
    this.CpuLoadSimMax = 0;
    this.CpuLoadSimAvg = 0;
    this.CpuLoadDraw = 0;
    this.CpuLoadDrawMax = 0;
    this.CpuLoadDrawAvg = 0;
    this.CpuLoadFrame = 0;
    this.CpuLoadFrameMax = 0;
    this.CpuLoadFrameAvg = 0;
    this.Fps = 0;
    this.FpsMax = 0;
    this.FpsMin = 0;
    this.CurrCpuLoadSimMax = 0;
    this.CurrCpuLoadFrameMax = 0;
    this.CurrFpsMax = 0;
    this.CurrFpsMin = 1e9;
  }

  this.ResetFuncs.Call( this );
}

Sim.prototype.Stop = function( reset ) {
  this.Running = false;
  if (this.AnimationFrame) {
    cancelAnimationFrame( this.AnimationFrame );
    this.AnimationFrame = null;
  }

  this.StopFuncs.Call( this );
  if (reset) {
    this.Reset();
  }
}

Sim.prototype.Run = function( reset ) {
  if (reset) {
    if (this.Running) {
      this.Stop();
    }
    this.Reset();
  }
  this.RunFuncs.Call( this );
  if (this.Running) return;

  this.StartTime = xTimeMS();
  this.LastFrameTime = this.StartTime;
  this.LastSampleTime = this.StartTime;
  this.Running = true;
  this.NFrames = 0;
  var me = this;
  this.AnimationFrame = requestAnimationFrame(
    function CB_OnAnimationFrame() {
      me.OnAnimationFrame();
    }
  );
}

Sim.prototype.Pause = function( reset ) {
  if (this.Running) {
    this.Stop();
  } else {
    this.Run( reset );
  }
}

Sim.prototype.InitStates = function( states, dim, size ) {
  // states: State = { OldAccel, Accel, Speed, Pos } or array of State
  // OldAccel, Accel, Speed, Pos: Number or array of Number
  // dim: integer; dimension of State properties; 0 -> meaning they are Numbers, else they are arrays
  // size: integer; size of states array; 0 -> meaning states is a State object, not an array (vector)
  //
  // initializes states.OldAccel from states.Accel
  // call this function before using CompNewStates() the first time

  if (size > 0) {
    for (var i = 0; i < size; i++) {
      this.InitStates( states, dim, 0 );
    }
    return;
  }
  // assert size = 0 and states is a State object
  if (dim > 0) {
    for (var i = 0; i < dim; i++) {
      states.OldAccel[i] = states.Accel[i];
    }
  } else {
    states.OldAccel = states.Accel;
  }
}

Sim.prototype.CompNewStates = function( states, dim, size ) {
  // states: State = { OldAccel, Accel, Speed, Pos } or array of State
  // OldAccel, Accel, Speed, Pos: Number or array of Number
  // dim: integer; dimension of State properties; 0 -> meaning they are Numbers, else they are arrays
  // size: integer; size of states array; 0 -> meaning states is a State object, not an array (vector)
  //
  // you have to provide current calculated acceleration (eg from sum of forces) in states.Accel
  // from that states.Speed and states.Pos is computed by numerical integration
  // OldAccel stores current Accel for next step of integration for more accuracy
  // numerical integration uses this.DeltaTime as the time step
  // requires that states.OldAccel is valid, by calling InitStates()

  if (size > 0) {
    for (var i = 0; i < size; i++) {
      this.CompNewStates( states[i], dim, 0 );
    }
    return;
  }
  // assert size = 0 and states is a State object
  if (dim > 0) {
    for (var i = 0; i < dim; i++) {
      var deltaAccel = states.Accel[i] - states.OldAccel[i];
      states.OldAccel[i] = states.Accel[i];
      var deltaSpeed = ((0.5 * deltaAccel) + states.Accel[i]) * this.DeltaTime;
      states.Speed[i] += deltaSpeed;
      var deltaPos = ((0.5 * deltaSpeed) + states.Speed[i]) * this.DeltaTime;
      states.Pos[i] += deltaPos;
    }
  } else {
    var deltaAccel = states.Accel - states.OldAccel;
    states.OldAccel = states.Accel;
    var deltaSpeed = ((0.5 * deltaAccel) + states.Accel) * this.DeltaTime;
    states.Speed += deltaSpeed;
    var deltaPos = ((0.5 * deltaSpeed) + states.Speed) * this.DeltaTime;
    states.Pos += deltaPos;
  }
}

Sim.prototype.OnAnimationFrame = function() {
  if (!this.Running || this.IsInAnimationFrame) return;

  if (this.AnimationFrame) {
    cancelAnimationFrame( this.AnimationFrame );
    this.AnimationFrame = null;
  }
  this.NFrames++;
  this.IsInAnimationFrame = true;

  var frameStartTime_ms = xTimeMS();
  var frameRealTime_s = (frameStartTime_ms - this.LastFrameTime) / 1000;
  var estimatedFps = 1 / frameRealTime_s;
  var maxFrameRealTime_s = 1 / this.TargetFps;
  if (maxFrameRealTime_s > frameRealTime_s) maxFrameRealTime_s = frameRealTime_s;
  // assert( maxFrameRealTime_s(TargetFps) <= frameRealTime_s(Fps) )

  var maxFrameSimulTime_s = maxFrameRealTime_s * this.TimeSpeed;
  var nTimeSteps = Math.floor( maxFrameSimulTime_s / this.TimeStep ) + 1;
  this.DeltaTime = maxFrameSimulTime_s / nTimeSteps;
  var maxFrameCpuTime_ms = maxFrameRealTime_s * 1000;

  var frameSimulTime_s = 0;
  var frameCpuTime_ms = 0;
  maxFrameSimulTime_s -= this.DeltaTime / 2;

  while ( frameSimulTime_s < maxFrameSimulTime_s && frameCpuTime_ms <= maxFrameCpuTime_ms ) {
    frameSimulTime_s += this.DeltaTime;
    this.SimulTime += this.DeltaTime;

    this.TimeStepFuncs.Call( this );  // -----------> time step callback

    frameCpuTime_ms = xTimeMS() - frameStartTime_ms;
  }

  // simulation statistics
  if (this.EnableStatistics && this.NFrames > 1) {

    // current simulation cpu load
    if (frameCpuTime_ms >= maxFrameCpuTime_ms) {
      this.CpuLoadSim = 1;
    } else {
      this.CpuLoadSim = frameCpuTime_ms / maxFrameCpuTime_ms;
    }

    // average simulation cpu load and Fps
    var n = this.NAvgSamples;
    if (n <= 0) n = 2;
    this.CpuLoadSimAvg += (this.CpuLoadSim - this.CpuLoadSimAvg) / n;
    this.Fps += (estimatedFps - this.Fps) / n;

    // simulation max cpu load and min/max Fps
    var currSampleTime_ms = frameStartTime_ms - this.LastSampleTime;
    if (currSampleTime_ms > this.SampleTime) {
      this.CpuLoadSimMax = this.CurrCpuLoadSimMax;
      this.CurrCpuLoadSimMax = 0;
      this.FpsMax = this.CurrFpsMax;
      this.CurrFpsMax = 0;
      this.FpsMin = this.CurrFpsMin;
      this.CurrFpsMin = 1e9;
    }
    if (this.CpuLoadSim > this.CurrCpuLoadSimMax) this.CurrCpuLoadSimMax = this.CpuLoadSim;
    if (this.Fps > this.CurrFpsMax) this.CurrFpsMax = this.Fps;
    if (this.Fps < this.CurrFpsMin) this.CurrFpsMin = this.Fps;
  }

  this.LastFrameTime = frameStartTime_ms;
  this.RealTime = (frameStartTime_ms - this.StartTime) / 1000;

  this.FrameFuncs.Call( this );  // -----------> frame callback

  this.IsInAnimationFrame = false;

  // frame statistics
  if (this.EnableStatistics && this.NFrames > 1) {

    // current frame cpu load
    frameCpuTime_ms = xTimeMS() - frameStartTime_ms;
    if (frameCpuTime_ms >= maxFrameCpuTime_ms) {
      this.CpuLoadFrame = 1;
    } else {
      this.CpuLoadFrame = frameCpuTime_ms / maxFrameCpuTime_ms;
    }

    // average frame cpu laod
    var n = this.NAvgSamples;
    if (n <= 0) n = 2;
    this.CpuLoadFrameAvg += this.CpuLoadFrame / n - this.CpuLoadFrameAvg / n;

    // frame max cpu load
    if (frameStartTime_ms - this.LastSampleTime > this.SampleTime) {
      this.LastSampleTime = frameStartTime_ms;
      this.CpuLoadFrameMax = this.CurrCpuLoadFrameMax;
      this.CurrCpuLoadFrameMax = 0;
    }
    if (this.CpuLoadFrame > this.CurrCpuLoadFrameMax) this.CurrCpuLoadFrameMax = this.CpuLoadFrame;

    // draw cpu load as diff of frame cpu load and sim cpu load
    this.CpuLoadDraw = this.CpuLoadFrame - this.CpuLoadSim;
    if (this.CpuLoadDraw < 0) this.CpuLoadDraw = 0;
    this.CpuLoadDrawMax = this.CpuLoadFrameMax - this.CpuLoadSimMax;
    if (this.CpuLoadDrawMax < 0) this.CpuLoadDrawMax = 0;
    this.CpuLoadDrawAvg = this.CpuLoadFrameAvg - this.CpuLoadSimAvg;
    if (this.CpuLoadDrawAvg < 0) this.CpuLoadDrawAvg = 0;

  }

  var me = this;
  this.AnimationFrame = requestAnimationFrame(
    function CB_OnAnimationFrame() {
      me.OnAnimationFrame();
    }
  );
}


More Page Infos / Sitemap
Created Sonntag, 8. Oktober 2017
Scroll to Top of Page
Changed Sonntag, 8. Oktober 2017