@dra2020/baseclient 1.0.122 → 1.0.124

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1921,12 +1921,12 @@ class FsmManager {
1921
1921
  this.theId = 0;
1922
1922
  this.theEpoch = 0;
1923
1923
  this.bTickSet = false;
1924
- this.theTickList = {};
1924
+ this.theTickList = new Set();
1925
1925
  this.theBusyLoopCount = 0;
1926
1926
  this.doTick = this.doTick.bind(this);
1927
1927
  }
1928
1928
  forceTick(fsm) {
1929
- this.theTickList[fsm.id] = fsm;
1929
+ this.theTickList.add(fsm);
1930
1930
  if (!this.bTickSet) {
1931
1931
  this.bTickSet = true;
1932
1932
  doLater(this.doTick);
@@ -1935,18 +1935,13 @@ class FsmManager {
1935
1935
  doTick() {
1936
1936
  this.bTickSet = false;
1937
1937
  let nLoops = 0;
1938
- while (nLoops < 1 && !Util.isEmpty(this.theTickList)) {
1938
+ while (nLoops < 1 && this.theTickList.size > 0) {
1939
1939
  nLoops++;
1940
1940
  let thisTickList = this.theTickList;
1941
- this.theTickList = {};
1942
- for (let id in thisTickList)
1943
- if (thisTickList.hasOwnProperty(id)) {
1944
- let f = thisTickList[id];
1945
- f.preTick();
1946
- f.tick();
1947
- }
1941
+ this.theTickList = new Set();
1942
+ thisTickList.forEach((f) => f.tick());
1948
1943
  }
1949
- if (Util.isEmpty(this.theTickList))
1944
+ if (this.theTickList.size == 0)
1950
1945
  this.theBusyLoopCount = 0;
1951
1946
  else
1952
1947
  this.theBusyLoopCount++;
@@ -1961,49 +1956,28 @@ class Fsm {
1961
1956
  this.state = exports.FSM_STARTING;
1962
1957
  this.dependentError = false;
1963
1958
  this.epochDone = -1;
1964
- this._waitOn = null;
1965
- this._waitedOn = null;
1959
+ this._waitOn = new Set();
1960
+ this._waitedOn = new Set();
1966
1961
  this.manager.forceTick(this);
1967
1962
  }
1968
1963
  get env() { return this._env; }
1969
1964
  get manager() { return this.env.fsmManager; }
1970
- get done() {
1971
- return FsmDone(this.state);
1972
- }
1973
- get ready() {
1974
- return !this.done && this._waitOn == null;
1975
- }
1976
- get iserror() {
1977
- return (this.state === exports.FSM_ERROR || this.state === exports.FSM_CANCEL);
1978
- }
1979
- get isDependentError() {
1980
- return this.dependentError;
1981
- }
1982
- cancel() {
1983
- // Override if you need to do more than marking complete
1984
- this.setState(exports.FSM_CANCEL);
1985
- }
1986
- setDependentError() {
1987
- this.dependentError = true;
1988
- }
1989
- clearDependentError() {
1990
- this.dependentError = false;
1991
- }
1992
- get ticked() {
1993
- return this.done && this.manager.theEpoch > this.epochDone;
1994
- }
1995
- get nWaitOn() {
1996
- return Util.countKeys(this._waitOn);
1997
- }
1998
- get nWaitedOn() {
1999
- return Util.countKeys(this._waitedOn);
2000
- }
1965
+ get done() { return FsmDone(this.state); }
1966
+ get ready() { return !this.done && this.nWaitOn == 0; }
1967
+ get iserror() { return this.state === exports.FSM_ERROR || this.state === exports.FSM_CANCEL; }
1968
+ get isDependentError() { return this.dependentError; }
1969
+ // Override if you need to do more than marking complete
1970
+ cancel() { this.setState(exports.FSM_CANCEL); }
1971
+ setDependentError() { this.dependentError = true; }
1972
+ clearDependentError() { this.dependentError = false; }
1973
+ get ticked() { return this.done && this.manager.theEpoch > this.epochDone; }
1974
+ get nWaitOn() { return this._waitOn.size; }
1975
+ get nWaitedOn() { return this._waitedOn.size; }
2001
1976
  waitOn(fsm) {
2002
1977
  if (fsm == null)
2003
1978
  return this;
2004
1979
  else if (Array.isArray(fsm)) {
2005
- for (let i = 0; i < fsm.length; i++)
2006
- this.waitOn(fsm[i]);
1980
+ fsm.forEach((f) => this.waitOn(f));
2007
1981
  }
2008
1982
  else {
2009
1983
  if (fsm.done) {
@@ -2015,12 +1989,8 @@ class Fsm {
2015
1989
  this.setDependentError();
2016
1990
  }
2017
1991
  else {
2018
- if (this._waitOn == null)
2019
- this._waitOn = {};
2020
- this._waitOn[fsm.id] = fsm;
2021
- if (fsm._waitedOn == null)
2022
- fsm._waitedOn = {};
2023
- fsm._waitedOn[this.id] = this;
1992
+ this._waitOn.add(fsm);
1993
+ fsm._waitedOn.add(this);
2024
1994
  }
2025
1995
  }
2026
1996
  return this;
@@ -2028,16 +1998,11 @@ class Fsm {
2028
1998
  setState(state) {
2029
1999
  this.state = state;
2030
2000
  if (this.done) {
2031
- while (this._waitedOn) {
2032
- let on = this._waitedOn;
2033
- this._waitedOn = null;
2034
- for (let id in on)
2035
- if (on.hasOwnProperty(id)) {
2036
- let f = on[id];
2037
- if (this.iserror)
2038
- f.setDependentError();
2039
- this.manager.forceTick(f);
2040
- }
2001
+ // Loop because completion might add more to wait list
2002
+ while (this.nWaitedOn) {
2003
+ let waitedOn = this._waitedOn;
2004
+ this._waitedOn = new Set();
2005
+ waitedOn.forEach((f) => { f._completed(this); this.manager.forceTick(f); });
2041
2006
  }
2042
2007
  this.epochDone = this.manager.theEpoch;
2043
2008
  }
@@ -2047,21 +2012,17 @@ class Fsm {
2047
2012
  end(state = exports.FSM_DONE) {
2048
2013
  this.setState(state);
2049
2014
  }
2050
- // Cleans up _waitOn
2051
- preTick() {
2052
- if (this._waitOn == null)
2053
- return;
2054
- let bMore = false;
2055
- for (let id in this._waitOn)
2056
- if (this._waitOn.hasOwnProperty(id)) {
2057
- let fsm = this._waitOn[id];
2058
- if (fsm.done)
2059
- delete this._waitOn[id];
2060
- else
2061
- bMore = true;
2062
- }
2063
- if (!bMore)
2064
- this._waitOn = null;
2015
+ // Override if subclass needs to respond to individual items completing rather than waiting
2016
+ // in tick. Useful to avoid O(n^2) if waiting on lots of items and not wanting to do a linear
2017
+ // scan for completed FSMs. Or to not require separate bookkeeping for list of dependents.
2018
+ waitOnCompleted(f) { }
2019
+ _completed(f) {
2020
+ if (f.iserror)
2021
+ this.setDependentError();
2022
+ if (f.done && this._waitOn.has(f)) {
2023
+ this._waitOn.delete(f);
2024
+ this.waitOnCompleted(f);
2025
+ }
2065
2026
  }
2066
2027
  tick() {
2067
2028
  }
@@ -4366,6 +4327,13 @@ class OTArrayLikeResource extends OT.OTResourceBase {
4366
4327
  this.edits = newA;
4367
4328
  this.coalesce();
4368
4329
  }
4330
+ tryCompose(rhs) {
4331
+ if (this.length == 0)
4332
+ return true;
4333
+ else if (rhs.edits.length == 0)
4334
+ return true;
4335
+ return this.finalLength() == rhs.originalLength();
4336
+ }
4369
4337
  performTransformReorder(bForceRetainBeforeInsert, newA, iBegin, iEnd) {
4370
4338
  if (iBegin < 0 || iBegin > iEnd)
4371
4339
  return;
@@ -4421,6 +4389,11 @@ class OTArrayLikeResource extends OT.OTResourceBase {
4421
4389
  (newA[i])[0] = exports.OpRetain;
4422
4390
  this.edits = newA;
4423
4391
  }
4392
+ tryTransform(prior, bPriorIsService) {
4393
+ if (this.length == 0 || prior.length == 0)
4394
+ return true;
4395
+ return this.originalLength() == prior.originalLength();
4396
+ }
4424
4397
  transform(prior, bPriorIsService) {
4425
4398
  if (this.length == 0 || prior.length == 0)
4426
4399
  return;
@@ -5101,6 +5074,16 @@ class OTCompositeResource extends OT.OTResourceBase {
5101
5074
  lhsEdit.transform(rhsEdit, bPriorIsService);
5102
5075
  }
5103
5076
  }
5077
+ tryTransform(rhs, bPriorIsService) {
5078
+ let bSucceed = true;
5079
+ for (let i = 0; bSucceed && i < rhs.length; i++) {
5080
+ let rhsEdit = rhs.edits[i];
5081
+ let lhsEdit = this.findResource(rhsEdit.resourceName, rhsEdit.underlyingType, false);
5082
+ if (lhsEdit)
5083
+ bSucceed = lhsEdit.tryTransform(rhsEdit, bPriorIsService);
5084
+ }
5085
+ return bSucceed;
5086
+ }
5104
5087
  // compose two edit actions
5105
5088
  compose(rhs) {
5106
5089
  for (let i = 0; i < rhs.length; i++) {
@@ -5112,6 +5095,16 @@ class OTCompositeResource extends OT.OTResourceBase {
5112
5095
  this.clock = rhs.clock;
5113
5096
  this.clientSequenceNo = rhs.clientSequenceNo;
5114
5097
  }
5098
+ tryCompose(rhs) {
5099
+ let bSucceed = true;
5100
+ for (let i = 0; bSucceed && i < rhs.length; i++) {
5101
+ let rhsEdit = rhs.edits[i];
5102
+ let lhsEdit = this.findResource(rhsEdit.resourceName, rhsEdit.underlyingType, !rhsEdit.isEmpty());
5103
+ if (lhsEdit)
5104
+ bSucceed = lhsEdit.tryCompose(rhsEdit);
5105
+ }
5106
+ return bSucceed;
5107
+ }
5115
5108
  // apply this edit to an existing value, returning new value (if underlying type is mutable, may modify input)
5116
5109
  // For composite, takes array of values, returns array of results, one for each underlying resource.
5117
5110
  apply(runningValue) {
@@ -5718,8 +5711,35 @@ class OTServerEngine extends OTE.OTEngine {
5718
5711
  let aPrior = this.logServer[i].copy();
5719
5712
  for (i++; i < this.logServer.length; i++)
5720
5713
  aPrior.compose(this.logServer[i]);
5714
+ if (!a.tryTransform(aPrior, true)) {
5715
+ console.log('otserverengine: rejecting event on transform failure');
5716
+ this.forgetEvents(orig);
5717
+ return OTS.EClockReset;
5718
+ }
5721
5719
  a.transform(aPrior, true);
5722
5720
  }
5721
+ let bFail = !this.stateServer.tryCompose(a);
5722
+ if (bFail) {
5723
+ console.log('otserverengine: rejecting event on compose failure');
5724
+ if (this.logServer.length) {
5725
+ let newState = this.logServer[0].copy();
5726
+ for (let i = 1; i < this.logServer.length; i++)
5727
+ newState.compose(this.logServer[i]);
5728
+ if (newState.tryCompose(a)) {
5729
+ console.log('otserverengine: actually... rejected event would succeed on composed log, so patching');
5730
+ this.stateServer = newState;
5731
+ bFail = false;
5732
+ }
5733
+ else
5734
+ console.log('otserverengine: and... rejected event also fails on composed log');
5735
+ }
5736
+ else
5737
+ console.log('otserverengine: and... no log to try to patch state with');
5738
+ if (bFail) {
5739
+ this.forgetEvents(orig);
5740
+ return OTS.EClockReset;
5741
+ }
5742
+ }
5723
5743
  a.clock = this.stateServer.clock + 1;
5724
5744
  this.stateServer.compose(a);
5725
5745
  this.resetCaches();
@@ -5933,10 +5953,18 @@ class OTResourceBase {
5933
5953
  transform(rhs, bPriorIsService) {
5934
5954
  throw "OTResourceBase.transform must be overridden in subclass";
5935
5955
  }
5956
+ // Test if transform would succeed, false on failure
5957
+ tryTransform(rhs, bPriorIsServer) {
5958
+ return true;
5959
+ }
5936
5960
  // compose two edit actions
5937
5961
  compose(rhs) {
5938
5962
  throw "OTResourceBase.compose must be overridden in subclass";
5939
5963
  }
5964
+ // test compose
5965
+ tryCompose(rhs) {
5966
+ return true;
5967
+ }
5940
5968
  // apply this edit to an existing value, returning new value (if underlying type is mutable, may modify input)
5941
5969
  apply(startValue) {
5942
5970
  throw "OTResourceBase.apply must be overridden in subclass";