@sdeverywhere/runtime 0.2.5 → 0.2.7

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.
package/dist/index.js CHANGED
@@ -207,7 +207,7 @@ function encodeVarIndices(varSpecs, indicesArray) {
207
207
  }
208
208
  }
209
209
  function getEncodedLookupBufferLengths(lookupDefs) {
210
- var _a;
210
+ var _a, _b;
211
211
  let lookupIndicesLength = 1;
212
212
  let lookupsLength = 0;
213
213
  for (const lookupDef of lookupDefs) {
@@ -219,7 +219,7 @@ function getEncodedLookupBufferLengths(lookupDefs) {
219
219
  const subCount = ((_a = varSpec.subscriptIndices) == null ? void 0 : _a.length) || 0;
220
220
  lookupIndicesLength += subCount;
221
221
  lookupIndicesLength += 2;
222
- lookupsLength += lookupDef.points.length;
222
+ lookupsLength += ((_b = lookupDef.points) == null ? void 0 : _b.length) || 0;
223
223
  }
224
224
  return {
225
225
  lookupIndicesLength,
@@ -239,10 +239,15 @@ function encodeLookups(lookupDefs, lookupIndicesArray, lookupsArray) {
239
239
  for (let i = 0; i < subCount; i++) {
240
240
  lookupIndicesArray[li++] = subs[i];
241
241
  }
242
- lookupIndicesArray[li++] = lookupDataOffset;
243
- lookupIndicesArray[li++] = lookupDef.points.length;
244
- lookupsArray == null ? void 0 : lookupsArray.set(lookupDef.points, lookupDataOffset);
245
- lookupDataOffset += lookupDef.points.length;
242
+ if (lookupDef.points !== void 0) {
243
+ lookupIndicesArray[li++] = lookupDataOffset;
244
+ lookupIndicesArray[li++] = lookupDef.points.length;
245
+ lookupsArray == null ? void 0 : lookupsArray.set(lookupDef.points, lookupDataOffset);
246
+ lookupDataOffset += lookupDef.points.length;
247
+ } else {
248
+ lookupIndicesArray[li++] = -1;
249
+ lookupIndicesArray[li++] = 0;
250
+ }
246
251
  }
247
252
  }
248
253
  function decodeLookups(lookupIndicesArray, lookupsArray) {
@@ -263,10 +268,14 @@ function decodeLookups(lookupIndicesArray, lookupsArray) {
263
268
  subscriptIndices
264
269
  };
265
270
  let points;
266
- if (lookupsArray) {
267
- points = lookupsArray.slice(lookupDataOffset, lookupDataOffset + lookupDataLength);
271
+ if (lookupDataOffset >= 0) {
272
+ if (lookupsArray) {
273
+ points = lookupsArray.slice(lookupDataOffset, lookupDataOffset + lookupDataLength);
274
+ } else {
275
+ points = new Float64Array(0);
276
+ }
268
277
  } else {
269
- points = new Float64Array(0);
278
+ points = void 0;
270
279
  }
271
280
  lookupDefs.push({
272
281
  varRef: {
@@ -280,11 +289,14 @@ function decodeLookups(lookupIndicesArray, lookupsArray) {
280
289
 
281
290
  // src/_shared/lookup-def.ts
282
291
  function createLookupDef(varRef, points) {
283
- const flatPoints = new Float64Array(points.length * 2);
284
- let i = 0;
285
- for (const p of points) {
286
- flatPoints[i++] = p.x;
287
- flatPoints[i++] = p.y;
292
+ let flatPoints;
293
+ if (points) {
294
+ flatPoints = new Float64Array(points.length * 2);
295
+ let i = 0;
296
+ for (const p of points) {
297
+ flatPoints[i++] = p.x;
298
+ flatPoints[i++] = p.y;
299
+ }
288
300
  }
289
301
  return {
290
302
  varRef,
@@ -840,18 +852,59 @@ var _NA_ = -Number.MAX_VALUE;
840
852
  // src/js-model/js-model-lookup.ts
841
853
  var JsModelLookup = class {
842
854
  /**
843
- * @param n The number of (x,y) pairs in the lookup.
855
+ * @param size The number of (x,y) pairs in the lookup.
844
856
  * @param data The lookup data, as (x,y) pairs. The length of the array must be
845
857
  * >= 2*n. Note that the data will be stored by reference, so if there is a chance
846
858
  * that the array will be reused or modified by other code, be sure to pass in a
847
859
  * copy of the array.
848
860
  */
849
- constructor(n, data) {
850
- this.n = n;
851
- this.data = data;
852
- if (data.length < n * 2) {
853
- throw new Error(`Lookup data array length must be >= 2*size (length=${data.length} size=${n}`);
861
+ constructor(size, data) {
862
+ if (data && data.length < size * 2) {
863
+ throw new Error(`Lookup data array length must be >= 2*size (length=${data.length} size=${size}`);
864
+ }
865
+ this.originalData = data;
866
+ this.originalSize = size;
867
+ this.dynamicData = void 0;
868
+ this.dynamicSize = 0;
869
+ this.activeData = this.originalData;
870
+ this.activeSize = this.originalSize;
871
+ this.lastInput = Number.MAX_VALUE;
872
+ this.lastHitIndex = 0;
873
+ }
874
+ /**
875
+ * Set new data for this lookup instance, or restore the original data.
876
+ *
877
+ * If `data` is undefined, the original data that was supplied to the constructor will
878
+ * be restored as the "active" data. Otherwise, `data` will be copied to an internal
879
+ * data buffer, which will be the "active" data. If `size` is greater than the size
880
+ * passed to previous calls, the internal data buffer will be grown as needed.
881
+ *
882
+ * @param size The number of (x,y) pairs in the lookup.
883
+ * @param data The lookup data, as (x,y) pairs. The length of the array must be
884
+ * >= 2*n. Note that the data will be copied into an internal data buffer, so it
885
+ * is not necessary to defensively copy data before calling this method.
886
+ */
887
+ setData(size, data) {
888
+ if (data) {
889
+ if (data.length < size * 2) {
890
+ throw new Error(`Lookup data array length must be >= 2*size (length=${data.length} size=${size}`);
891
+ }
892
+ const dataLengthInElems = size * 2;
893
+ if (this.dynamicData === void 0 || dataLengthInElems > this.dynamicData.length) {
894
+ this.dynamicData = new Float64Array(dataLengthInElems);
895
+ }
896
+ this.dynamicSize = size;
897
+ if (size > 0) {
898
+ const subarray = data.subarray(0, dataLengthInElems);
899
+ this.dynamicData.set(subarray);
900
+ }
901
+ this.activeData = this.dynamicData;
902
+ this.activeSize = this.dynamicSize;
903
+ } else {
904
+ this.activeData = this.originalData;
905
+ this.activeSize = this.originalSize;
854
906
  }
907
+ this.invertedData = void 0;
855
908
  this.lastInput = Number.MAX_VALUE;
856
909
  this.lastHitIndex = 0;
857
910
  }
@@ -860,8 +913,8 @@ var JsModelLookup = class {
860
913
  }
861
914
  getValueForY(y) {
862
915
  if (this.invertedData === void 0) {
863
- const numValues = this.n * 2;
864
- const normalData = this.data;
916
+ const numValues = this.activeSize * 2;
917
+ const normalData = this.activeData;
865
918
  const invertedData = Array(numValues);
866
919
  for (let i = 0; i < numValues; i += 2) {
867
920
  invertedData[i] = normalData[i + 1];
@@ -876,11 +929,11 @@ var JsModelLookup = class {
876
929
  * NOTE: The x values are assumed to be monotonically increasing.
877
930
  */
878
931
  getValue(input, useInvertedData, mode) {
879
- if (this.n === 0) {
932
+ if (this.activeSize === 0) {
880
933
  return _NA_;
881
934
  }
882
- const data = useInvertedData ? this.invertedData : this.data;
883
- const max = this.n * 2;
935
+ const data = useInvertedData ? this.invertedData : this.activeData;
936
+ const max = this.activeSize * 2;
884
937
  const useCachedValues = !useInvertedData;
885
938
  let startIndex;
886
939
  if (useCachedValues && input >= this.lastInput) {
@@ -939,10 +992,10 @@ var JsModelLookup = class {
939
992
  * no points) or if the provided time is earlier than the first data point.
940
993
  */
941
994
  getValueForGameTime(time, defaultValue) {
942
- if (this.n <= 0) {
995
+ if (this.activeSize <= 0) {
943
996
  return defaultValue;
944
997
  }
945
- const x0 = this.data[0];
998
+ const x0 = this.activeData[0];
946
999
  if (time < x0) {
947
1000
  return defaultValue;
948
1001
  }
@@ -957,33 +1010,34 @@ var JsModelLookup = class {
957
1010
  * lookup behavior, so we implement it as a separate method here.
958
1011
  */
959
1012
  getValueBetweenTimes(input, mode) {
960
- if (this.n === 0) {
1013
+ if (this.activeSize === 0) {
961
1014
  return _NA_;
962
1015
  }
963
- const max = this.n * 2;
1016
+ const data = this.activeData;
1017
+ const max = this.activeSize * 2;
964
1018
  switch (mode) {
965
1019
  case "forward": {
966
1020
  input = Math.floor(input);
967
1021
  for (let xi = 0; xi < max; xi += 2) {
968
- const x = this.data[xi];
1022
+ const x = data[xi];
969
1023
  if (x >= input) {
970
- return this.data[xi + 1];
1024
+ return data[xi + 1];
971
1025
  }
972
1026
  }
973
- return this.data[max - 1];
1027
+ return data[max - 1];
974
1028
  }
975
1029
  case "backward": {
976
1030
  input = Math.floor(input);
977
1031
  for (let xi = 2; xi < max; xi += 2) {
978
- const x = this.data[xi];
1032
+ const x = data[xi];
979
1033
  if (x >= input) {
980
- return this.data[xi - 1];
1034
+ return data[xi - 1];
981
1035
  }
982
1036
  }
983
1037
  if (max >= 4) {
984
- return this.data[max - 3];
1038
+ return data[max - 3];
985
1039
  } else {
986
- return this.data[1];
1040
+ return data[1];
987
1041
  }
988
1042
  }
989
1043
  case "interpolate":
@@ -995,17 +1049,17 @@ var JsModelLookup = class {
995
1049
  throw new Error(msg);
996
1050
  }
997
1051
  for (let xi = 2; xi < max; xi += 2) {
998
- const x = this.data[xi];
1052
+ const x = data[xi];
999
1053
  if (x >= input) {
1000
- const last_x = this.data[xi - 2];
1001
- const last_y = this.data[xi - 1];
1002
- const y = this.data[xi + 1];
1054
+ const last_x = data[xi - 2];
1055
+ const last_y = data[xi - 1];
1056
+ const y = data[xi + 1];
1003
1057
  const dx = x - last_x;
1004
1058
  const dy = y - last_y;
1005
1059
  return last_y + dy / dx * (input - last_x);
1006
1060
  }
1007
1061
  }
1008
- return this.data[max - 1];
1062
+ return data[max - 1];
1009
1063
  }
1010
1064
  }
1011
1065
  }
@@ -1456,7 +1510,8 @@ var MockJsModel = class {
1456
1510
  if (varId === void 0) {
1457
1511
  throw new Error(`No lookup variable found for spec ${varSpec}`);
1458
1512
  }
1459
- this.lookups.set(varId, new JsModelLookup(points.length / 2, points));
1513
+ const numPoints = points ? points.length / 2 : 0;
1514
+ this.lookups.set(varId, new JsModelLookup(numPoints, points));
1460
1515
  }
1461
1516
  // from JsModel interface
1462
1517
  storeOutputs(storeValue) {
@@ -1585,14 +1640,21 @@ var WasmModel = class {
1585
1640
  } else {
1586
1641
  subIndicesAddress = 0;
1587
1642
  }
1588
- const numLookupElements = lookupDef.points.length;
1589
- if (this.lookupDataBuffer === void 0 || this.lookupDataBuffer.numElements < numLookupElements) {
1590
- (_c = this.lookupDataBuffer) == null ? void 0 : _c.dispose();
1591
- this.lookupDataBuffer = createFloat64WasmBuffer(this.wasmModule, numLookupElements);
1643
+ let pointsAddress;
1644
+ let numPoints;
1645
+ if (lookupDef.points) {
1646
+ const numLookupElements = lookupDef.points.length;
1647
+ if (this.lookupDataBuffer === void 0 || this.lookupDataBuffer.numElements < numLookupElements) {
1648
+ (_c = this.lookupDataBuffer) == null ? void 0 : _c.dispose();
1649
+ this.lookupDataBuffer = createFloat64WasmBuffer(this.wasmModule, numLookupElements);
1650
+ }
1651
+ this.lookupDataBuffer.getArrayView().set(lookupDef.points);
1652
+ pointsAddress = this.lookupDataBuffer.getAddress();
1653
+ numPoints = numLookupElements / 2;
1654
+ } else {
1655
+ pointsAddress = 0;
1656
+ numPoints = 0;
1592
1657
  }
1593
- this.lookupDataBuffer.getArrayView().set(lookupDef.points);
1594
- const pointsAddress = this.lookupDataBuffer.getAddress();
1595
- const numPoints = numLookupElements / 2;
1596
1658
  const varIndex = varSpec.varIndex;
1597
1659
  this.wasmSetLookup(varIndex, subIndicesAddress, pointsAddress, numPoints);
1598
1660
  }
@@ -1800,7 +1862,7 @@ var ModelScheduler = class {
1800
1862
  /** Whether a model run is in progress. */
1801
1863
  this.runInProgress = false;
1802
1864
  const afterSet = () => {
1803
- this.runWasmModelIfNeeded();
1865
+ this.runModelIfNeeded();
1804
1866
  };
1805
1867
  for (const userInput of userInputs) {
1806
1868
  userInput.callbacks.onSet = afterSet;
@@ -1811,24 +1873,24 @@ var ModelScheduler = class {
1811
1873
  }
1812
1874
  }
1813
1875
  /**
1814
- * Schedule a wasm model run (if not already pending). When the run is
1876
+ * Schedule a model run (if not already pending). When the run is
1815
1877
  * complete, save the outputs and call the `onOutputsChanged` callback.
1816
1878
  */
1817
- runWasmModelIfNeeded() {
1879
+ runModelIfNeeded() {
1818
1880
  this.runNeeded = true;
1819
1881
  if (this.runInProgress) {
1820
1882
  return;
1821
1883
  } else {
1822
1884
  this.runInProgress = true;
1823
1885
  setTimeout(() => {
1824
- this.runWasmModelNow();
1886
+ this.runModelNow();
1825
1887
  }, 0);
1826
1888
  }
1827
1889
  }
1828
1890
  /**
1829
- * Run the wasm model asynchronously using the current set of input values.
1891
+ * Run the model asynchronously using the current set of input values.
1830
1892
  */
1831
- runWasmModelNow() {
1893
+ runModelNow() {
1832
1894
  return __async(this, null, function* () {
1833
1895
  var _a;
1834
1896
  for (let i = 0; i < this.userInputs.length; i++) {
@@ -1843,7 +1905,7 @@ var ModelScheduler = class {
1843
1905
  if (this.runNeeded) {
1844
1906
  this.runNeeded = false;
1845
1907
  setTimeout(() => {
1846
- this.runWasmModelNow();
1908
+ this.runModelNow();
1847
1909
  }, 0);
1848
1910
  } else {
1849
1911
  this.runNeeded = false;
@@ -1865,12 +1927,199 @@ function createSimpleInputValue(varId) {
1865
1927
  };
1866
1928
  return { varId, get, set, reset, callbacks: {} };
1867
1929
  }
1930
+
1931
+ // src/model-scheduler/multi-context-model-scheduler.ts
1932
+ var MultiContextModelScheduler = class {
1933
+ /**
1934
+ * @param runner The model runner.
1935
+ * @param options Additional options for the scheduler.
1936
+ * @param options.initialOutputs An optional `Outputs` instance that will be reused
1937
+ * for the initial context. This is useful for saving memory when an `Outputs`
1938
+ * instance was already created for, e.g., a initial baseline/reference run.
1939
+ */
1940
+ constructor(runner, options) {
1941
+ this.runner = runner;
1942
+ /** The contexts that hold distinct sets of inputs and outputs. */
1943
+ this.contexts = [];
1944
+ /** Whether a model run has been scheduled. */
1945
+ this.runNeeded = false;
1946
+ /** Whether a model run is in progress. */
1947
+ this.runInProgress = false;
1948
+ this.initialOutputs = options == null ? void 0 : options.initialOutputs;
1949
+ }
1950
+ /**
1951
+ * Return true if the scheduler has started any model runs.
1952
+ */
1953
+ isStarted() {
1954
+ return this.initialOutputs === void 0;
1955
+ }
1956
+ /**
1957
+ * Add a new context that holds a distinct set of model inputs and outputs.
1958
+ * These inputs and outputs are kept separate from those in other contexts,
1959
+ * which allows an application to use the same underlying model to run with
1960
+ * multiple I/O contexts.
1961
+ *
1962
+ * Note that the contexts created before the first scheduled model run
1963
+ * will inherit the data from `initialOutputs` passed to the constructor,
1964
+ * but contexts created after that will initially have output values set
1965
+ * to zero.
1966
+ *
1967
+ * @param inputs The input values, in the same order as in the spec file passed to `sde`.
1968
+ * @param options Additional options for the context.
1969
+ * @param options.externalData Additional data that is external to the model outputs.
1970
+ * For example, this can contain data that was captured from an initial reference
1971
+ * run, or other static data that is displayed in graphs alongside the model
1972
+ * output data in graphs.
1973
+ */
1974
+ addContext(inputs, options) {
1975
+ let outputs;
1976
+ if (this.initialOutputs !== void 0) {
1977
+ if (this.contexts.length === 0) {
1978
+ outputs = this.initialOutputs;
1979
+ } else {
1980
+ outputs = this.runner.createOutputs();
1981
+ for (const varId of outputs.varIds) {
1982
+ const series0 = this.initialOutputs.getSeriesForVar(varId);
1983
+ const series1 = outputs.getSeriesForVar(varId);
1984
+ for (let i = 0; i < series0.points.length; i++) {
1985
+ series1.points[i].y = series0.points[i].y;
1986
+ }
1987
+ }
1988
+ }
1989
+ } else {
1990
+ outputs = this.runner.createOutputs();
1991
+ }
1992
+ const context = new ModelContextImpl(inputs, outputs, options == null ? void 0 : options.externalData);
1993
+ const afterSet = () => {
1994
+ context.runNeeded = true;
1995
+ this.runModelIfNeeded();
1996
+ };
1997
+ for (const input of inputs) {
1998
+ input.callbacks.onSet = afterSet;
1999
+ }
2000
+ this.contexts.push(context);
2001
+ return context;
2002
+ }
2003
+ /**
2004
+ * Remove the given context from the set of contexts managed by the scheduler.
2005
+ *
2006
+ * @param context The context to remove.
2007
+ */
2008
+ removeContext(context) {
2009
+ const index = this.contexts.findIndex((c) => c === context);
2010
+ if (index >= 0) {
2011
+ this.contexts.splice(index, 1);
2012
+ }
2013
+ }
2014
+ /**
2015
+ * Schedule a model run (if not already pending). When the run is
2016
+ * complete, save the outputs and call the `onOutputsChanged` callback.
2017
+ */
2018
+ runModelIfNeeded() {
2019
+ this.runNeeded = true;
2020
+ if (this.runInProgress) {
2021
+ return;
2022
+ } else {
2023
+ this.runInProgress = true;
2024
+ setTimeout(() => {
2025
+ this.runModelNow();
2026
+ }, 0);
2027
+ }
2028
+ }
2029
+ /**
2030
+ * Run the model asynchronously for all relevant contexts.
2031
+ */
2032
+ runModelNow() {
2033
+ return __async(this, null, function* () {
2034
+ this.initialOutputs = void 0;
2035
+ for (const context of this.contexts) {
2036
+ if (context.runNeeded) {
2037
+ context.runNeeded = false;
2038
+ yield this.runModelNowForContext(context);
2039
+ }
2040
+ }
2041
+ if (this.runNeeded) {
2042
+ this.runNeeded = false;
2043
+ setTimeout(() => {
2044
+ this.runModelNow();
2045
+ }, 0);
2046
+ } else {
2047
+ this.runNeeded = false;
2048
+ this.runInProgress = false;
2049
+ }
2050
+ });
2051
+ }
2052
+ /**
2053
+ * Run the model asynchronously using the current set of input values in the given context.
2054
+ *
2055
+ * @param context The context to use for the model run.
2056
+ */
2057
+ runModelNowForContext(context) {
2058
+ return __async(this, null, function* () {
2059
+ var _a;
2060
+ if (this.currentInputs === void 0) {
2061
+ this.currentInputs = Array(context.inputsArray.length);
2062
+ }
2063
+ for (let i = 0; i < context.inputsArray.length; i++) {
2064
+ this.currentInputs[i] = context.inputsArray[i].get();
2065
+ }
2066
+ try {
2067
+ yield this.runner.runModel(this.currentInputs, context.outputs);
2068
+ (_a = context.onOutputsChanged) == null ? void 0 : _a.call(context);
2069
+ } catch (e) {
2070
+ console.error("ERROR: The scheduler encountered an error when running the model:", e);
2071
+ }
2072
+ });
2073
+ }
2074
+ };
2075
+ var ModelContextImpl = class {
2076
+ /**
2077
+ * @hidden This is intended for use by `MultiContextModelScheduler` only.
2078
+ *
2079
+ * @param inputs The input values, in the same order as in the spec file passed to `sde`.
2080
+ * @param outputs The structure into which the model outputs will be stored.
2081
+ * @param externalData Additional data that is external to the model outputs. For example, this can contain
2082
+ * data that was captured from an initial reference run, or other static data that is displayed in graphs
2083
+ * alongside the model output data in graphs.
2084
+ */
2085
+ constructor(inputs, outputs, externalData) {
2086
+ this.externalData = externalData;
2087
+ /**
2088
+ * Whether a model run is needed for this context.
2089
+ * @hidden This is intended for use by `MultiContextModelScheduler` only.
2090
+ */
2091
+ this.runNeeded = false;
2092
+ this.inputsArray = Array.from(inputs);
2093
+ this.outputs = outputs;
2094
+ }
2095
+ /**
2096
+ * Return the series data for the given model output variable or external
2097
+ * dataset.
2098
+ *
2099
+ * @param varId The ID of the output variable associated with the data.
2100
+ * @param sourceName The external data source name (e.g. "Ref"), or
2101
+ * undefined to use the latest model output data from this context.
2102
+ */
2103
+ getSeriesForVar(varId, sourceName) {
2104
+ if (sourceName === void 0) {
2105
+ return this.outputs.getSeriesForVar(varId);
2106
+ } else {
2107
+ const dataForSource = this.externalData.get(sourceName);
2108
+ if (dataForSource !== void 0) {
2109
+ return dataForSource.get(varId);
2110
+ } else {
2111
+ return void 0;
2112
+ }
2113
+ }
2114
+ }
2115
+ };
1868
2116
  export {
1869
2117
  BufferedRunModelParams,
1870
2118
  MockJsModel,
1871
2119
  MockWasmModule,
1872
2120
  ModelListing,
1873
2121
  ModelScheduler,
2122
+ MultiContextModelScheduler,
1874
2123
  Outputs,
1875
2124
  ReferencedRunModelParams,
1876
2125
  Series,