@vived/core 2.0.1 → 2.0.2

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.
@@ -0,0 +1,340 @@
1
+ # Entities
2
+
3
+ The Entities folder provides foundational classes for building reactive, observable data models. It includes the Observer pattern implementation and a suite of memoized wrapper classes for various data types.
4
+
5
+ ## Overview
6
+
7
+ This module solves two key problems in reactive application development:
8
+
9
+ 1. **Change Notification** - Notify observers when data changes (Observer pattern)
10
+ 2. **Performance Optimization** - Only trigger callbacks when values actually change (memoization)
11
+
12
+ ## Core Components
13
+
14
+ ### Observer Pattern
15
+
16
+ #### ObservableEntity
17
+
18
+ Abstract base class implementing the Observer pattern. Extend this to make any entity observable.
19
+
20
+ ```typescript
21
+ import { ObservableEntity } from "@vived/core";
22
+
23
+ export class MyEntity extends ObservableEntity
24
+ {
25
+ private _name: string = "";
26
+
27
+ get name(): string
28
+ {
29
+ return this._name;
30
+ }
31
+
32
+ set name(value: string)
33
+ {
34
+ this._name = value;
35
+ this.notify(); // Notify all observers
36
+ }
37
+ }
38
+
39
+ // Usage
40
+ const entity = new MyEntity();
41
+ entity.addObserver(() => console.log("Entity changed!"));
42
+ entity.name = "New Name"; // Logs: "Entity changed!"
43
+ ```
44
+
45
+ #### ObserverList
46
+
47
+ Generic implementation of the Observer pattern with typed messages. Use this when you need to pass data to observers.
48
+
49
+ ```typescript
50
+ import { ObserverList } from "@vived/core";
51
+
52
+ const observers = new ObserverList<string>();
53
+
54
+ observers.add((message) => console.log(`Received: ${message}`));
55
+ observers.notify("Hello World"); // Logs: "Received: Hello World"
56
+
57
+ // Check observer count
58
+ console.log(observers.length); // 1
59
+
60
+ // Clear all observers
61
+ observers.clear();
62
+ ```
63
+
64
+ ### Memoized Wrappers
65
+
66
+ Memoized classes wrap values and only trigger callbacks when the value actually changes. This prevents unnecessary re-renders and computations.
67
+
68
+ #### MemoizedBoolean
69
+
70
+ Wrapper for boolean values with change detection.
71
+
72
+ ```typescript
73
+ import { MemoizedBoolean } from "@vived/core";
74
+
75
+ const isVisible = new MemoizedBoolean(
76
+ false,
77
+ () => console.log("Visibility changed")
78
+ );
79
+
80
+ isVisible.val = true; // Logs: "Visibility changed"
81
+ isVisible.val = true; // No callback - value didn't change
82
+ isVisible.val = false; // Logs: "Visibility changed"
83
+
84
+ // Set without triggering callback
85
+ isVisible.setValQuietly(true);
86
+ ```
87
+
88
+ #### MemoizedNumber
89
+
90
+ Wrapper for numeric values with change detection.
91
+
92
+ ```typescript
93
+ import { MemoizedNumber } from "@vived/core";
94
+
95
+ const count = new MemoizedNumber(
96
+ 0,
97
+ () => console.log("Count changed")
98
+ );
99
+
100
+ count.val = 5; // Logs: "Count changed"
101
+ count.val = 5; // No callback - value didn't change
102
+ ```
103
+
104
+ #### MemoizedString
105
+
106
+ Wrapper for string values with change detection.
107
+
108
+ ```typescript
109
+ import { MemoizedString } from "@vived/core";
110
+
111
+ const title = new MemoizedString(
112
+ "Untitled",
113
+ () => console.log("Title changed")
114
+ );
115
+
116
+ title.val = "New Title"; // Logs: "Title changed"
117
+ title.val = "New Title"; // No callback - value didn't change
118
+ ```
119
+
120
+ #### MemoizedAngle
121
+
122
+ Wrapper for Angle value objects with change detection.
123
+
124
+ ```typescript
125
+ import { MemoizedAngle, Angle } from "@vived/core";
126
+
127
+ const rotation = new MemoizedAngle(
128
+ Angle.FromDegrees(0),
129
+ () => console.log("Rotation changed")
130
+ );
131
+
132
+ rotation.val = Angle.FromDegrees(45);
133
+ ```
134
+
135
+ #### MemoizedVector2
136
+
137
+ Wrapper for 2D vector value objects with change detection.
138
+
139
+ ```typescript
140
+ import { MemoizedVector2, Vector2 } from "@vived/core";
141
+
142
+ const position = new MemoizedVector2(
143
+ Vector2.Zero(),
144
+ () => console.log("Position changed")
145
+ );
146
+
147
+ position.val = new Vector2(10, 20);
148
+ ```
149
+
150
+ #### MemoizedVector3
151
+
152
+ Wrapper for 3D vector value objects with change detection.
153
+
154
+ ```typescript
155
+ import { MemoizedVector3, Vector3 } from "@vived/core";
156
+
157
+ const position = new MemoizedVector3(
158
+ Vector3.Zero(),
159
+ () => console.log("Position changed")
160
+ );
161
+
162
+ position.val = new Vector3(10, 20, 30);
163
+ ```
164
+
165
+ #### MemoizedQuaternion
166
+
167
+ Wrapper for quaternion rotation value objects with change detection.
168
+
169
+ ```typescript
170
+ import { MemoizedQuaternion, Quaternion } from "@vived/core";
171
+
172
+ const rotation = new MemoizedQuaternion(
173
+ Quaternion.Identity(),
174
+ () => console.log("Rotation changed")
175
+ );
176
+
177
+ rotation.val = Quaternion.FromEuler(0, Math.PI / 2, 0);
178
+ ```
179
+
180
+ #### MemoizedColor
181
+
182
+ Wrapper for color value objects with change detection.
183
+
184
+ ```typescript
185
+ import { MemoizedColor, Color } from "@vived/core";
186
+
187
+ const backgroundColor = new MemoizedColor(
188
+ Color.White(),
189
+ () => console.log("Color changed")
190
+ );
191
+
192
+ backgroundColor.val = Color.FromRGB(255, 0, 0);
193
+ ```
194
+
195
+ ### Range-Constrained Values
196
+
197
+ #### RangedNumber
198
+
199
+ Numeric value that is automatically clamped to a specified range.
200
+
201
+ ```typescript
202
+ import { RangedNumber } from "@vived/core";
203
+
204
+ const volume = new RangedNumber(
205
+ {
206
+ min: 0,
207
+ max: 100,
208
+ initialValue: 50
209
+ },
210
+ () => console.log("Volume changed")
211
+ );
212
+
213
+ volume.val = 75; // Sets to 75
214
+ volume.val = 150; // Clamped to 100
215
+ volume.val = -10; // Clamped to 0
216
+
217
+ console.log(volume.minValue); // 0
218
+ console.log(volume.maxValue); // 100
219
+ ```
220
+
221
+ ## Common Patterns
222
+
223
+ ### Building Reactive Entities
224
+
225
+ Combine memoized values within an observable entity:
226
+
227
+ ```typescript
228
+ import { ObservableEntity, MemoizedString, MemoizedNumber } from "@vived/core";
229
+
230
+ export class User extends ObservableEntity
231
+ {
232
+ readonly name: MemoizedString;
233
+ readonly age: MemoizedNumber;
234
+
235
+ constructor(name: string, age: number)
236
+ {
237
+ super();
238
+ this.name = new MemoizedString(name, () => this.notify());
239
+ this.age = new MemoizedNumber(age, () => this.notify());
240
+ }
241
+ }
242
+
243
+ // Usage
244
+ const user = new User("John", 30);
245
+ user.addObserver(() => console.log("User updated"));
246
+
247
+ user.name.val = "Jane"; // Triggers observer
248
+ user.age.val = 31; // Triggers observer
249
+ ```
250
+
251
+ ### Silent Updates
252
+
253
+ Use `setValQuietly()` when you need to update values without triggering side effects:
254
+
255
+ ```typescript
256
+ const count = new MemoizedNumber(0, () => updateUI());
257
+
258
+ // Update without triggering UI update
259
+ count.setValQuietly(10);
260
+ ```
261
+
262
+ ### Managing Observers
263
+
264
+ ```typescript
265
+ const entity = new MyEntity();
266
+
267
+ // Add observer
268
+ const observer = () => console.log("Changed");
269
+ entity.addObserver(observer);
270
+
271
+ // Remove specific observer
272
+ entity.removeObserver(observer);
273
+
274
+ // Using ObserverList directly
275
+ const observers = new ObserverList<void>();
276
+ observers.add(observer);
277
+ observers.remove(observer);
278
+ observers.clear(); // Remove all
279
+ ```
280
+
281
+ ## Design Principles
282
+
283
+ ### Memoization
284
+
285
+ All memoized classes compare values before triggering callbacks:
286
+ - **Primitives** (boolean, number, string) use strict equality (`===`)
287
+ - **Value Objects** (Vector, Color, etc.) use type-specific equality methods
288
+ - This prevents unnecessary callback execution and improves performance
289
+
290
+ ### Immutability Considerations
291
+
292
+ Value objects (Vector3, Color, etc.) should be treated as immutable. Always create new instances rather than mutating existing ones:
293
+
294
+ ```typescript
295
+ // Good
296
+ position.val = new Vector3(position.val.x + 1, position.val.y, position.val.z);
297
+
298
+ // Bad - mutation won't trigger change detection
299
+ position.val.x += 1;
300
+ ```
301
+
302
+ ## Testing
303
+
304
+ All memoized and observable classes include comprehensive test coverage demonstrating:
305
+ - Initial value storage
306
+ - Change detection
307
+ - Callback triggering
308
+ - Silent updates
309
+ - Range clamping (for RangedNumber)
310
+
311
+ Example test pattern:
312
+
313
+ ```typescript
314
+ it("only triggers callback when value changes", () => {
315
+ const callback = jest.fn();
316
+ const memoized = new MemoizedNumber(5, callback);
317
+
318
+ memoized.val = 5; // No change
319
+ expect(callback).not.toBeCalled();
320
+
321
+ memoized.val = 10; // Changed
322
+ expect(callback).toBeCalledTimes(1);
323
+ });
324
+ ```
325
+
326
+ ## Use Cases
327
+
328
+ - **State Management** - Track application state with automatic change notifications
329
+ - **View Models** - Build presentation models that notify views when data changes
330
+ - **Form Inputs** - Validate and constrain user input with RangedNumber
331
+ - **Animation** - Track animated values and trigger updates only when they change
332
+ - **Configuration** - Manage settings with type-safe, observable properties
333
+ - **Game Objects** - Track entity properties (position, rotation, color) with change detection
334
+
335
+ ## Performance Benefits
336
+
337
+ - **Reduced Re-renders** - UI updates only when values actually change
338
+ - **Efficient Validation** - Range constraints applied automatically
339
+ - **Lazy Computation** - Callbacks only fire when necessary
340
+ - **Memory Efficient** - Lightweight wrappers with minimal overhead