@woosh/meep-engine 2.84.5 → 2.84.8
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/build/meep.cjs +139 -84
- package/build/meep.min.js +1 -1
- package/build/meep.module.js +139 -84
- package/package.json +1 -1
- package/src/core/collection/array/arrayIndexByEquality.js +1 -0
- package/src/core/collection/list/List.js +26 -18
- package/src/core/collection/list/List.spec.js +73 -0
- package/src/core/primitives/numbers/number_count_decimals.js +30 -0
- package/src/core/primitives/numbers/number_pretty_print.js +7 -29
- package/src/core/primitives/strings/string_format_camel_to_kebab.js +2 -0
- package/src/core/primitives/strings/string_strip_trailing.js +22 -0
- package/src/core/primitives/strings/string_strip_trailing.spec.js +27 -0
- package/src/core/process/worker/WorkerBuilder.js +5 -1
- package/src/core/process/worker/WorkerProxy.js +3 -2
- package/src/engine/ecs/Entity.spec.js +33 -0
- package/src/engine/ecs/EntityComponentDataset.js +17 -11
- package/src/engine/ecs/storage/BinaryBufferDeSerializer.js +8 -1
- package/src/engine/graphics/camera/CameraShake.js +1 -127
- package/src/engine/graphics/camera/CameraShakeBehavior.js +91 -0
- package/src/engine/graphics/camera/CameraShakeTraumaBehavior.js +38 -0
- package/src/engine/graphics/ecs/animation/animator/AnimationGraphSystem.js +9 -23
- package/src/engine/graphics/ecs/animation/animator/blending/BlendStateMatrix.js +11 -6
- package/src/engine/graphics/ecs/animation/animator/graph/AnimationGraph.js +20 -12
- package/src/engine/graphics/ecs/animation/animator/graph/AnimationState.js +3 -3
- package/src/engine/graphics/ecs/path/tube/build/estimatePathViaIterativeIntegral.js +1 -1
- package/src/view/View.js +52 -30
- package/src/view/writeCssTransformMatrix.js +26 -0
package/build/meep.module.js
CHANGED
|
@@ -61116,6 +61116,7 @@ function objectsEqual(a, b) {
|
|
|
61116
61116
|
}
|
|
61117
61117
|
|
|
61118
61118
|
/**
|
|
61119
|
+
* Works similarly to `Array.prototype.indexOf`, but instead of strict equality - uses provided equality method
|
|
61119
61120
|
* @template T
|
|
61120
61121
|
* @param {T[]} array
|
|
61121
61122
|
* @param {T} element
|
|
@@ -61219,7 +61220,7 @@ class List {
|
|
|
61219
61220
|
this.data = array !== undefined ? array.slice() : [];
|
|
61220
61221
|
|
|
61221
61222
|
/**
|
|
61222
|
-
*
|
|
61223
|
+
* Number of elements in the list
|
|
61223
61224
|
* @type {number}
|
|
61224
61225
|
*/
|
|
61225
61226
|
this.length = this.data.length;
|
|
@@ -61269,7 +61270,7 @@ class List {
|
|
|
61269
61270
|
this.data.push(el);
|
|
61270
61271
|
const oldLength = this.length;
|
|
61271
61272
|
|
|
61272
|
-
this.length
|
|
61273
|
+
this.length = oldLength + 1;
|
|
61273
61274
|
|
|
61274
61275
|
this.on.added.send2(el, oldLength);
|
|
61275
61276
|
return this;
|
|
@@ -61788,30 +61789,30 @@ class List {
|
|
|
61788
61789
|
}
|
|
61789
61790
|
|
|
61790
61791
|
/**
|
|
61791
|
-
*
|
|
61792
|
+
* @deprecated use `#reset` directly in combination with `this.on.removed` signal
|
|
61792
61793
|
* @param {function(element:T,index:number)} callback
|
|
61793
61794
|
* @param {*} [thisArg]
|
|
61794
61795
|
*/
|
|
61795
61796
|
resetViaCallback(callback, thisArg) {
|
|
61797
|
+
|
|
61796
61798
|
const length = this.length;
|
|
61797
|
-
if (length > 0) {
|
|
61798
61799
|
|
|
61799
|
-
|
|
61800
|
+
const removed = this.on.removed;
|
|
61801
|
+
|
|
61802
|
+
const data = this.data;
|
|
61800
61803
|
|
|
61801
|
-
|
|
61804
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
61805
|
+
const element = data[i];
|
|
61802
61806
|
|
|
61803
|
-
//
|
|
61804
|
-
|
|
61805
|
-
|
|
61806
|
-
// decrement data length gradually to allow handlers access to the rest of the elements
|
|
61807
|
-
this.data.length = i;
|
|
61808
|
-
this.length = i;
|
|
61809
|
-
removed.send2(element, i);
|
|
61807
|
+
// decrement data length gradually to allow handlers access to the rest of the elements
|
|
61808
|
+
data.length = i;
|
|
61809
|
+
this.length = i;
|
|
61810
61810
|
|
|
61811
|
-
|
|
61812
|
-
}
|
|
61811
|
+
removed.send2(element, i);
|
|
61813
61812
|
|
|
61813
|
+
callback.call(thisArg, element, i);
|
|
61814
61814
|
}
|
|
61815
|
+
|
|
61815
61816
|
}
|
|
61816
61817
|
|
|
61817
61818
|
reset() {
|
|
@@ -62031,11 +62032,19 @@ class List {
|
|
|
62031
62032
|
}
|
|
62032
62033
|
|
|
62033
62034
|
/**
|
|
62034
|
-
*
|
|
62035
|
+
* First element in the list
|
|
62036
|
+
* @returns {T|undefined}
|
|
62037
|
+
*/
|
|
62038
|
+
first() {
|
|
62039
|
+
return this.get(0);
|
|
62040
|
+
}
|
|
62041
|
+
|
|
62042
|
+
/**
|
|
62043
|
+
* Last element in the list
|
|
62035
62044
|
* @return {T|undefined}
|
|
62036
62045
|
*/
|
|
62037
62046
|
last() {
|
|
62038
|
-
return this.
|
|
62047
|
+
return this.get(this.length - 1);
|
|
62039
62048
|
}
|
|
62040
62049
|
|
|
62041
62050
|
/**
|
|
@@ -63119,10 +63128,11 @@ class WorkerProxy {
|
|
|
63119
63128
|
}
|
|
63120
63129
|
|
|
63121
63130
|
/**
|
|
63131
|
+
* Invoke a given method on the worker, as defined by the `WorkerBuilder`
|
|
63122
63132
|
* @template T
|
|
63123
|
-
* @param {number} name
|
|
63133
|
+
* @param {number} name Method's name
|
|
63124
63134
|
* @param {Array} args
|
|
63125
|
-
* @return {Promise<T>}
|
|
63135
|
+
* @return {Promise<T>} eventual result of the invoked method
|
|
63126
63136
|
*/
|
|
63127
63137
|
$submitRequest(name, args) {
|
|
63128
63138
|
const pending = this.__pending[name];
|
|
@@ -63345,6 +63355,10 @@ class WorkerBuilder {
|
|
|
63345
63355
|
functions = [];
|
|
63346
63356
|
preamble = new LineBuilder();
|
|
63347
63357
|
|
|
63358
|
+
/**
|
|
63359
|
+
*
|
|
63360
|
+
* @param {string} code
|
|
63361
|
+
*/
|
|
63348
63362
|
addCode(code) {
|
|
63349
63363
|
this.preamble.add(code);
|
|
63350
63364
|
}
|
|
@@ -79747,28 +79761,19 @@ function m3_cm_compose_transform(
|
|
|
79747
79761
|
}
|
|
79748
79762
|
|
|
79749
79763
|
/**
|
|
79750
|
-
*
|
|
79751
|
-
* @
|
|
79764
|
+
* Smallest safe increment for a Float32
|
|
79765
|
+
* @see https://www.cplusplus.com/reference/cfloat/
|
|
79766
|
+
* @see https://bitbashing.io/comparing-floats.html
|
|
79767
|
+
* @type {number}
|
|
79752
79768
|
*/
|
|
79753
|
-
|
|
79754
|
-
|
|
79755
|
-
|
|
79756
|
-
const scratch_m3_0 = new Float32Array(9);
|
|
79757
|
-
|
|
79769
|
+
const FLT_EPSILON_32 = 1.192092896E-7;
|
|
79770
|
+
|
|
79758
79771
|
/**
|
|
79759
|
-
*
|
|
79760
|
-
* @param
|
|
79761
|
-
* @param {
|
|
79762
|
-
* @param {Vector2} scale
|
|
79763
|
-
* @param {number} rotation angle in radians
|
|
79772
|
+
*
|
|
79773
|
+
* @param {Float32Array} m3
|
|
79774
|
+
* @param {HTMLElement} domElement
|
|
79764
79775
|
*/
|
|
79765
|
-
function
|
|
79766
|
-
|
|
79767
|
-
const m3 = scratch_m3_0;
|
|
79768
|
-
|
|
79769
|
-
m3_cm_compose_transform(m3, position.x, position.y, scale.x, scale.y, 0, 0, rotation);
|
|
79770
|
-
|
|
79771
|
-
|
|
79776
|
+
function writeCssTransformMatrix(m3, domElement) {
|
|
79772
79777
|
/*
|
|
79773
79778
|
* CSS matrix is:
|
|
79774
79779
|
* a c e
|
|
@@ -79788,7 +79793,13 @@ function setElementTransform(domElement, position, scale, rotation) {
|
|
|
79788
79793
|
const style = domElement.style;
|
|
79789
79794
|
|
|
79790
79795
|
style.transform = transform;
|
|
79791
|
-
}
|
|
79796
|
+
}
|
|
79797
|
+
|
|
79798
|
+
/**
|
|
79799
|
+
* @author Alex Goldring, 2018
|
|
79800
|
+
* @copyright Alex Goldring 2018
|
|
79801
|
+
*/
|
|
79802
|
+
|
|
79792
79803
|
|
|
79793
79804
|
/**
|
|
79794
79805
|
*
|
|
@@ -79827,6 +79838,9 @@ const INITIAL_FLAGS = ViewFlags.Visible;
|
|
|
79827
79838
|
* @class
|
|
79828
79839
|
*/
|
|
79829
79840
|
class View {
|
|
79841
|
+
#transform_written = new Float32Array(9);
|
|
79842
|
+
#transform_current = new Float32Array(9);
|
|
79843
|
+
|
|
79830
79844
|
/**
|
|
79831
79845
|
* @constructor
|
|
79832
79846
|
*/
|
|
@@ -79851,31 +79865,32 @@ class View {
|
|
|
79851
79865
|
this.flags = INITIAL_FLAGS;
|
|
79852
79866
|
|
|
79853
79867
|
/**
|
|
79854
|
-
*
|
|
79868
|
+
* @readonly
|
|
79855
79869
|
* @type {Vector2}
|
|
79856
79870
|
*/
|
|
79857
79871
|
const position = this.position = new Vector2(0, 0);
|
|
79858
79872
|
|
|
79859
79873
|
/**
|
|
79860
|
-
*
|
|
79874
|
+
* @readonly
|
|
79861
79875
|
* @type {Vector1}
|
|
79862
79876
|
*/
|
|
79863
79877
|
const rotation = this.rotation = new Vector1(0);
|
|
79864
79878
|
|
|
79865
79879
|
/**
|
|
79866
|
-
*
|
|
79880
|
+
* @readonly
|
|
79867
79881
|
* @type {Vector2}
|
|
79868
79882
|
*/
|
|
79869
79883
|
const scale = this.scale = new Vector2(1, 1);
|
|
79870
79884
|
|
|
79871
79885
|
/**
|
|
79872
|
-
*
|
|
79886
|
+
* @readonly
|
|
79873
79887
|
* @type {Vector2}
|
|
79874
79888
|
*/
|
|
79875
79889
|
const size = this.size = new Vector2(0, 0);
|
|
79876
79890
|
|
|
79877
79891
|
/**
|
|
79878
79892
|
* Origin from which rotation and scaling is applied
|
|
79893
|
+
* @readonly
|
|
79879
79894
|
* @type {Vector2}
|
|
79880
79895
|
*/
|
|
79881
79896
|
this.transformOrigin = new Vector2(0.5, 0.5);
|
|
@@ -80013,7 +80028,41 @@ class View {
|
|
|
80013
80028
|
* @private
|
|
80014
80029
|
*/
|
|
80015
80030
|
__updateTransform() {
|
|
80016
|
-
|
|
80031
|
+
const position = this.position;
|
|
80032
|
+
const scale = this.scale;
|
|
80033
|
+
const rotation = this.rotation.getValue();
|
|
80034
|
+
|
|
80035
|
+
m3_cm_compose_transform(this.#transform_current, position.x, position.y, scale.x, scale.y, 0, 0, rotation);
|
|
80036
|
+
|
|
80037
|
+
this.#tryWriteTransform();
|
|
80038
|
+
}
|
|
80039
|
+
|
|
80040
|
+
#tryWriteTransform() {
|
|
80041
|
+
|
|
80042
|
+
const current = this.#transform_current;
|
|
80043
|
+
const written = this.#transform_written;
|
|
80044
|
+
|
|
80045
|
+
for (let i = 0; i < 9; i++) {
|
|
80046
|
+
const a = current[i];
|
|
80047
|
+
const b = written[i];
|
|
80048
|
+
|
|
80049
|
+
if (epsilonEquals(a, b, FLT_EPSILON_32)) {
|
|
80050
|
+
// common path
|
|
80051
|
+
continue;
|
|
80052
|
+
}
|
|
80053
|
+
|
|
80054
|
+
this.#writeTransform();
|
|
80055
|
+
return true;
|
|
80056
|
+
|
|
80057
|
+
}
|
|
80058
|
+
|
|
80059
|
+
return false;
|
|
80060
|
+
}
|
|
80061
|
+
|
|
80062
|
+
#writeTransform() {
|
|
80063
|
+
writeCssTransformMatrix(this.#transform_current, this.el);
|
|
80064
|
+
|
|
80065
|
+
this.#transform_written.set(this.#transform_current);
|
|
80017
80066
|
}
|
|
80018
80067
|
|
|
80019
80068
|
/**
|
|
@@ -93077,10 +93126,10 @@ class EntityComponentDataset {
|
|
|
93077
93126
|
|
|
93078
93127
|
/**
|
|
93079
93128
|
*
|
|
93080
|
-
* @param {number}
|
|
93129
|
+
* @param {number} entity_id
|
|
93081
93130
|
*/
|
|
93082
|
-
removeEntity(
|
|
93083
|
-
if (!this.entityExists(
|
|
93131
|
+
removeEntity(entity_id) {
|
|
93132
|
+
if (!this.entityExists(entity_id)) {
|
|
93084
93133
|
// entity doesn't exist
|
|
93085
93134
|
return;
|
|
93086
93135
|
}
|
|
@@ -93088,26 +93137,31 @@ class EntityComponentDataset {
|
|
|
93088
93137
|
const componentOccupancy = this.componentOccupancy;
|
|
93089
93138
|
const typeCount = this.componentTypeCount;
|
|
93090
93139
|
|
|
93091
|
-
const occupancyStart =
|
|
93140
|
+
const occupancyStart = entity_id * typeCount;
|
|
93092
93141
|
const occupancyEnd = occupancyStart + typeCount;
|
|
93093
93142
|
|
|
93094
|
-
|
|
93143
|
+
// remove all components from the entity
|
|
93144
|
+
for (
|
|
93145
|
+
let i = componentOccupancy.nextSetBit(occupancyStart);
|
|
93146
|
+
i < occupancyEnd && i !== -1;
|
|
93147
|
+
i = componentOccupancy.nextSetBit(i + 1)
|
|
93148
|
+
) {
|
|
93095
93149
|
const componentIndex = i % typeCount;
|
|
93096
|
-
this.removeComponentFromEntityByIndex_Unchecked(
|
|
93150
|
+
this.removeComponentFromEntityByIndex_Unchecked(entity_id, componentIndex, i);
|
|
93097
93151
|
}
|
|
93098
93152
|
|
|
93099
93153
|
//dispatch event
|
|
93100
|
-
this.sendEvent(
|
|
93154
|
+
this.sendEvent(entity_id, EventType.EntityRemoved, entity_id);
|
|
93101
93155
|
|
|
93102
93156
|
//purge all event listeners
|
|
93103
|
-
delete this.__entityEventListeners[
|
|
93104
|
-
delete this.__entityAnyEventListeners[
|
|
93157
|
+
delete this.__entityEventListeners[entity_id];
|
|
93158
|
+
delete this.__entityAnyEventListeners[entity_id];
|
|
93105
93159
|
|
|
93106
|
-
this.entityOccupancy.set(
|
|
93160
|
+
this.entityOccupancy.set(entity_id, false);
|
|
93107
93161
|
|
|
93108
93162
|
this.entityCount--;
|
|
93109
93163
|
|
|
93110
|
-
this.onEntityRemoved.send1(
|
|
93164
|
+
this.onEntityRemoved.send1(entity_id);
|
|
93111
93165
|
}
|
|
93112
93166
|
|
|
93113
93167
|
/**
|
|
@@ -93157,6 +93211,7 @@ class EntityComponentDataset {
|
|
|
93157
93211
|
}
|
|
93158
93212
|
|
|
93159
93213
|
/**
|
|
93214
|
+
* This method doesn't perform any checks, make sure you understand what you are doing when using it
|
|
93160
93215
|
* @private
|
|
93161
93216
|
* @param {number} entityIndex
|
|
93162
93217
|
* @param {number} componentIndex
|
|
@@ -96084,40 +96139,38 @@ LinearValue.prototype.fromJSON = function (json) {
|
|
|
96084
96139
|
|
|
96085
96140
|
/**
|
|
96086
96141
|
*
|
|
96087
|
-
* @param {
|
|
96088
|
-
* @param {string}
|
|
96142
|
+
* @param {string} string
|
|
96143
|
+
* @param {string} trailing_sequence
|
|
96089
96144
|
* @returns {string}
|
|
96090
96145
|
*/
|
|
96091
|
-
function
|
|
96146
|
+
function string_strip_trailing(string, trailing_sequence) {
|
|
96147
|
+
const trailing_sequence_length = trailing_sequence.length;
|
|
96092
96148
|
|
|
96093
|
-
|
|
96149
|
+
if (trailing_sequence_length <= 0) {
|
|
96150
|
+
// special case to avoid infinite looping
|
|
96151
|
+
return string;
|
|
96152
|
+
}
|
|
96153
|
+
|
|
96154
|
+
let end_index = string.length;
|
|
96155
|
+
|
|
96156
|
+
while (string.substring(end_index - trailing_sequence_length, end_index) === trailing_sequence) {
|
|
96157
|
+
end_index -= trailing_sequence_length;
|
|
96158
|
+
}
|
|
96159
|
+
|
|
96160
|
+
return string.substring(0, end_index);
|
|
96094
96161
|
}
|
|
96095
96162
|
|
|
96096
96163
|
/**
|
|
96097
96164
|
*
|
|
96098
|
-
* @param {
|
|
96099
|
-
* @
|
|
96165
|
+
* @param {number} x
|
|
96166
|
+
* @param {string} [separator=',']
|
|
96167
|
+
* @returns {string}
|
|
96100
96168
|
*/
|
|
96101
|
-
function
|
|
96102
|
-
if (value % 1 === 0) {
|
|
96103
|
-
//whole number
|
|
96104
|
-
return 0;
|
|
96105
|
-
}
|
|
96106
|
-
const s = value.toString();
|
|
96107
|
-
const index = s.indexOf('.');
|
|
96108
|
-
|
|
96109
|
-
if (index === -1) {
|
|
96110
|
-
return 0;
|
|
96111
|
-
}
|
|
96112
|
-
|
|
96113
|
-
//find last 0
|
|
96114
|
-
let endIndex = s.length - 1;
|
|
96115
|
-
for (; endIndex > index && s.charAt(endIndex) === "0"; endIndex--) {
|
|
96116
|
-
|
|
96117
|
-
}
|
|
96118
|
-
return endIndex - index;
|
|
96119
|
-
}
|
|
96169
|
+
function number_format_by_thousands(x, separator = ',') {
|
|
96120
96170
|
|
|
96171
|
+
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator);
|
|
96172
|
+
}
|
|
96173
|
+
|
|
96121
96174
|
/**
|
|
96122
96175
|
*
|
|
96123
96176
|
* @param {number} value
|
|
@@ -96128,10 +96181,12 @@ function number_pretty_print(value) {
|
|
|
96128
96181
|
const MAX_DECIMALS = 2;
|
|
96129
96182
|
|
|
96130
96183
|
const fraction = value % 1;
|
|
96131
|
-
|
|
96132
|
-
|
|
96133
|
-
|
|
96134
|
-
|
|
96184
|
+
|
|
96185
|
+
const would_produce_decimals = fraction * Math.pow(10, MAX_DECIMALS) > 0;
|
|
96186
|
+
|
|
96187
|
+
if (would_produce_decimals && Math.abs(value) < 100) {
|
|
96188
|
+
const truncated = value.toFixed(MAX_DECIMALS);
|
|
96189
|
+
return string_strip_trailing(truncated, "0");
|
|
96135
96190
|
} else {
|
|
96136
96191
|
//no fraction
|
|
96137
96192
|
return number_format_by_thousands(value - fraction, ",");
|
package/package.json
CHANGED
|
@@ -43,7 +43,7 @@ class List {
|
|
|
43
43
|
this.data = array !== undefined ? array.slice() : [];
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
46
|
+
* Number of elements in the list
|
|
47
47
|
* @type {number}
|
|
48
48
|
*/
|
|
49
49
|
this.length = this.data.length;
|
|
@@ -99,7 +99,7 @@ class List {
|
|
|
99
99
|
this.data.push(el);
|
|
100
100
|
const oldLength = this.length;
|
|
101
101
|
|
|
102
|
-
this.length
|
|
102
|
+
this.length = oldLength + 1;
|
|
103
103
|
|
|
104
104
|
this.on.added.send2(el, oldLength);
|
|
105
105
|
return this;
|
|
@@ -628,30 +628,30 @@ class List {
|
|
|
628
628
|
}
|
|
629
629
|
|
|
630
630
|
/**
|
|
631
|
-
*
|
|
631
|
+
* @deprecated use `#reset` directly in combination with `this.on.removed` signal
|
|
632
632
|
* @param {function(element:T,index:number)} callback
|
|
633
633
|
* @param {*} [thisArg]
|
|
634
634
|
*/
|
|
635
635
|
resetViaCallback(callback, thisArg) {
|
|
636
|
+
|
|
636
637
|
const length = this.length;
|
|
637
|
-
if (length > 0) {
|
|
638
638
|
|
|
639
|
-
|
|
639
|
+
const removed = this.on.removed;
|
|
640
640
|
|
|
641
|
-
|
|
641
|
+
const data = this.data;
|
|
642
642
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
const element = oldElements[i];
|
|
646
|
-
// decrement data length gradually to allow handlers access to the rest of the elements
|
|
647
|
-
this.data.length = i;
|
|
648
|
-
this.length = i;
|
|
649
|
-
removed.send2(element, i);
|
|
650
|
-
|
|
651
|
-
callback.call(thisArg, element, i);
|
|
652
|
-
}
|
|
643
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
644
|
+
const element = data[i];
|
|
653
645
|
|
|
646
|
+
// decrement data length gradually to allow handlers access to the rest of the elements
|
|
647
|
+
data.length = i;
|
|
648
|
+
this.length = i;
|
|
649
|
+
|
|
650
|
+
removed.send2(element, i);
|
|
651
|
+
|
|
652
|
+
callback.call(thisArg, element, i);
|
|
654
653
|
}
|
|
654
|
+
|
|
655
655
|
}
|
|
656
656
|
|
|
657
657
|
reset() {
|
|
@@ -875,11 +875,19 @@ class List {
|
|
|
875
875
|
}
|
|
876
876
|
|
|
877
877
|
/**
|
|
878
|
-
*
|
|
878
|
+
* First element in the list
|
|
879
|
+
* @returns {T|undefined}
|
|
880
|
+
*/
|
|
881
|
+
first() {
|
|
882
|
+
return this.get(0);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Last element in the list
|
|
879
887
|
* @return {T|undefined}
|
|
880
888
|
*/
|
|
881
889
|
last() {
|
|
882
|
-
return this.
|
|
890
|
+
return this.get(this.length - 1);
|
|
883
891
|
}
|
|
884
892
|
|
|
885
893
|
/**
|
|
@@ -9,6 +9,18 @@ class DummyNumber {
|
|
|
9
9
|
equals(o) {
|
|
10
10
|
return o.v === this.v;
|
|
11
11
|
}
|
|
12
|
+
|
|
13
|
+
copy(other) {
|
|
14
|
+
this.v = other.v;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
clone() {
|
|
18
|
+
const r = new DummyNumber();
|
|
19
|
+
|
|
20
|
+
r.copy(this);
|
|
21
|
+
|
|
22
|
+
return r;
|
|
23
|
+
}
|
|
12
24
|
}
|
|
13
25
|
|
|
14
26
|
test("constructor doesn't throw", () => {
|
|
@@ -254,4 +266,65 @@ test("patch", () => {
|
|
|
254
266
|
|
|
255
267
|
expect(list.asArray()).toEqual([3, 1, 2]);
|
|
256
268
|
expect(list.length).toBe(3);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("equals", () => {
|
|
272
|
+
|
|
273
|
+
expect(
|
|
274
|
+
new List().equals(new List())
|
|
275
|
+
).toBe(true);
|
|
276
|
+
|
|
277
|
+
expect(
|
|
278
|
+
new List(["a"]).equals(new List(["a"]))
|
|
279
|
+
).toBe(true);
|
|
280
|
+
|
|
281
|
+
expect(
|
|
282
|
+
new List(["a"]).equals(new List(["b"]))
|
|
283
|
+
).toBe(false);
|
|
284
|
+
|
|
285
|
+
expect(
|
|
286
|
+
new List(["a", "b"]).equals(new List(["a"]))
|
|
287
|
+
).toBe(false);
|
|
288
|
+
|
|
289
|
+
expect(
|
|
290
|
+
new List(["a", "b"]).equals(new List(["b"]))
|
|
291
|
+
).toBe(false);
|
|
292
|
+
|
|
293
|
+
expect(
|
|
294
|
+
new List(["a", "b"]).equals(new List(["a", "b"]))
|
|
295
|
+
).toBe(true);
|
|
296
|
+
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test("fist", () => {
|
|
300
|
+
|
|
301
|
+
expect(new List([1]).first()).toBe(1);
|
|
302
|
+
expect(new List([1, 2]).first()).toBe(1);
|
|
303
|
+
|
|
304
|
+
});
|
|
305
|
+
test("last", () => {
|
|
306
|
+
|
|
307
|
+
expect(new List([1]).last()).toBe(1);
|
|
308
|
+
expect(new List([1, 2]).last()).toBe(2);
|
|
309
|
+
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("deepCopy", () => {
|
|
313
|
+
|
|
314
|
+
const a = new List();
|
|
315
|
+
const b = new List();
|
|
316
|
+
|
|
317
|
+
b.deepCopy(a);
|
|
318
|
+
|
|
319
|
+
expect(b.isEmpty()).toBe(true);
|
|
320
|
+
|
|
321
|
+
const v = new DummyNumber(7);
|
|
322
|
+
b.addAll([v]);
|
|
323
|
+
|
|
324
|
+
a.deepCopy(b);
|
|
325
|
+
|
|
326
|
+
expect(a.length).toBe(1);
|
|
327
|
+
expect(a.first()).toBeDefined();
|
|
328
|
+
expect(a.first().v).toBe(7);
|
|
329
|
+
expect(a.first()).not.toBe(v);
|
|
257
330
|
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { assert } from "../../assert.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {number} value
|
|
6
|
+
* @returns {number}
|
|
7
|
+
*/
|
|
8
|
+
export function number_count_decimals(value) {
|
|
9
|
+
assert.isNumber(value, 'value');
|
|
10
|
+
|
|
11
|
+
if (value % 1 === 0) {
|
|
12
|
+
//whole number
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const s = value.toString();
|
|
17
|
+
const index = s.indexOf('.');
|
|
18
|
+
|
|
19
|
+
if (index === -1) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//find last 0
|
|
24
|
+
let endIndex = s.length - 1;
|
|
25
|
+
for (; endIndex > index && s.charAt(endIndex) === "0"; endIndex--) {
|
|
26
|
+
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return endIndex - index;
|
|
30
|
+
}
|
|
@@ -1,31 +1,7 @@
|
|
|
1
1
|
import { assert } from "../../assert.js";
|
|
2
|
+
import { string_strip_trailing } from "../strings/string_strip_trailing.js";
|
|
2
3
|
import { number_format_by_thousands } from "./number_format_by_thousands.js";
|
|
3
4
|
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
6
|
-
* @param {string} value
|
|
7
|
-
* @returns {number}
|
|
8
|
-
*/
|
|
9
|
-
function countDecimals(value) {
|
|
10
|
-
if (value % 1 === 0) {
|
|
11
|
-
//whole number
|
|
12
|
-
return 0;
|
|
13
|
-
}
|
|
14
|
-
const s = value.toString();
|
|
15
|
-
const index = s.indexOf('.');
|
|
16
|
-
|
|
17
|
-
if (index === -1) {
|
|
18
|
-
return 0;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
//find last 0
|
|
22
|
-
let endIndex = s.length - 1;
|
|
23
|
-
for (; endIndex > index && s.charAt(endIndex) === "0"; endIndex--) {
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
return endIndex - index;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
5
|
/**
|
|
30
6
|
*
|
|
31
7
|
* @param {number} value
|
|
@@ -37,10 +13,12 @@ export function number_pretty_print(value) {
|
|
|
37
13
|
const MAX_DECIMALS = 2;
|
|
38
14
|
|
|
39
15
|
const fraction = value % 1;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
16
|
+
|
|
17
|
+
const would_produce_decimals = fraction * Math.pow(10, MAX_DECIMALS) > 0;
|
|
18
|
+
|
|
19
|
+
if (would_produce_decimals && Math.abs(value) < 100) {
|
|
20
|
+
const truncated = value.toFixed(MAX_DECIMALS);
|
|
21
|
+
return string_strip_trailing(truncated, "0");
|
|
44
22
|
} else {
|
|
45
23
|
//no fraction
|
|
46
24
|
return number_format_by_thousands(value - fraction, ",");
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @param {string} string
|
|
4
|
+
* @param {string} trailing_sequence
|
|
5
|
+
* @returns {string}
|
|
6
|
+
*/
|
|
7
|
+
export function string_strip_trailing(string, trailing_sequence) {
|
|
8
|
+
const trailing_sequence_length = trailing_sequence.length;
|
|
9
|
+
|
|
10
|
+
if (trailing_sequence_length <= 0) {
|
|
11
|
+
// special case to avoid infinite looping
|
|
12
|
+
return string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let end_index = string.length;
|
|
16
|
+
|
|
17
|
+
while (string.substring(end_index - trailing_sequence_length, end_index) === trailing_sequence) {
|
|
18
|
+
end_index -= trailing_sequence_length;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return string.substring(0, end_index);
|
|
22
|
+
}
|