@energy8platform/game-engine 0.11.0 → 0.13.0
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/README.md +88 -1
- package/bin/simulate.ts +45 -5
- package/dist/index.cjs.js +49 -7
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.esm.js +49 -7
- package/dist/index.esm.js.map +1 -1
- package/dist/lua.cjs.js +237 -0
- package/dist/lua.cjs.js.map +1 -1
- package/dist/lua.d.ts +59 -2
- package/dist/lua.esm.js +236 -2
- package/dist/lua.esm.js.map +1 -1
- package/dist/react.cjs.js +667 -215
- package/dist/react.cjs.js.map +1 -1
- package/dist/react.esm.js +667 -215
- package/dist/react.esm.js.map +1 -1
- package/dist/ui.cjs.js +407 -7
- package/dist/ui.cjs.js.map +1 -1
- package/dist/ui.d.ts +156 -2
- package/dist/ui.esm.js +406 -8
- package/dist/ui.esm.js.map +1 -1
- package/package.json +4 -2
- package/scripts/install-simulate.mjs +100 -0
- package/src/lua/NativeSimulationRunner.ts +367 -0
- package/src/lua/index.ts +7 -0
- package/src/react/applyProps.ts +6 -4
- package/src/react/extendAll.ts +2 -0
- package/src/react/jsx.d.ts +28 -1
- package/src/react/reconciler.ts +58 -2
- package/src/ui/FlexContainer.ts +57 -7
- package/src/ui/Slider.ts +241 -0
- package/src/ui/Toggle.ts +201 -0
- package/src/ui/index.ts +5 -1
package/README.md
CHANGED
|
@@ -100,7 +100,7 @@ import { GameApplication } from '@energy8platform/game-engine'; // f
|
|
|
100
100
|
import { Scene, SceneManager } from '@energy8platform/game-engine/core';
|
|
101
101
|
import { AssetManager } from '@energy8platform/game-engine/assets';
|
|
102
102
|
import { AudioManager } from '@energy8platform/game-engine/audio';
|
|
103
|
-
import { FlexContainer, Button, Label, Panel, Modal, Layout, ScrollContainer, Toast, ProgressBar, BalanceDisplay, WinDisplay, resolveView } from '@energy8platform/game-engine/ui';
|
|
103
|
+
import { FlexContainer, Button, Label, Panel, Modal, Layout, ScrollContainer, Toast, ProgressBar, BalanceDisplay, WinDisplay, Slider, Toggle, resolveView } from '@energy8platform/game-engine/ui';
|
|
104
104
|
import { Tween, Timeline, Easing, SpriteAnimation } from '@energy8platform/game-engine/animation';
|
|
105
105
|
import { DevBridge, FPSOverlay } from '@energy8platform/game-engine/debug';
|
|
106
106
|
import { ReactScene, extendPixiElements, extendUIElements, useSDK, useViewport } from '@energy8platform/game-engine/react';
|
|
@@ -426,6 +426,26 @@ toolbar.addFlexChild(spacer, { flexGrow: 1 }); // with flex config
|
|
|
426
426
|
toolbar.resize(800, 60);
|
|
427
427
|
```
|
|
428
428
|
|
|
429
|
+
**FlexItemConfig** — per-child options passed via `addFlexChild(child, config)` or JSX props:
|
|
430
|
+
|
|
431
|
+
| Property | Type | Default | Description |
|
|
432
|
+
| --- | --- | --- | --- |
|
|
433
|
+
| `flexGrow` | `number` | `0` | Flex grow factor |
|
|
434
|
+
| `flexShrink` | `number` | `1` | Flex shrink factor (`0` = don't shrink when content overflows) |
|
|
435
|
+
| `alignSelf` | `'auto' \| 'start' \| 'center' \| 'end' \| 'stretch'` | `'auto'` | Override parent's `alignItems` for this child |
|
|
436
|
+
| `flexExclude` | `boolean` | `false` | Exclude from flex layout (like `position: absolute`) |
|
|
437
|
+
| `layoutWidth` | `number` | — | Explicit width override for layout calculations |
|
|
438
|
+
| `layoutHeight` | `number` | — | Explicit height override for layout calculations |
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
// Fixed item that won't shrink + centered override
|
|
442
|
+
toolbar.addFlexChild(logo, { flexShrink: 0 });
|
|
443
|
+
toolbar.addFlexChild(badge, { alignSelf: 'center' });
|
|
444
|
+
|
|
445
|
+
// Background excluded from layout flow
|
|
446
|
+
toolbar.addFlexChild(background, { flexExclude: true });
|
|
447
|
+
```
|
|
448
|
+
|
|
429
449
|
### Layout
|
|
430
450
|
|
|
431
451
|
Higher-level layout with direction presets (`horizontal`/`vertical`/`grid`/`wrap`), viewport anchoring, and responsive breakpoints. Wraps FlexContainer.
|
|
@@ -494,6 +514,58 @@ const bar = new ProgressBar({
|
|
|
494
514
|
});
|
|
495
515
|
```
|
|
496
516
|
|
|
517
|
+
### Slider
|
|
518
|
+
|
|
519
|
+
Draggable slider with customizable track, fill, and handle views:
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
// Graphics-based
|
|
523
|
+
const volume = new Slider({
|
|
524
|
+
min: 0, max: 1, value: 0.5, step: 0.1,
|
|
525
|
+
width: 200, height: 8,
|
|
526
|
+
fillColor: 0xffd700,
|
|
527
|
+
onUpdate: (v) => audio.setVolume('music', v),
|
|
528
|
+
onChange: (v) => console.log('Final:', v),
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// Asset-based
|
|
532
|
+
const volume = new Slider({
|
|
533
|
+
min: 0, max: 1, value: 0.5,
|
|
534
|
+
width: 200, height: 8,
|
|
535
|
+
trackView: 'slider-track',
|
|
536
|
+
fillView: 'slider-fill',
|
|
537
|
+
handleView: 'slider-handle',
|
|
538
|
+
onUpdate: (v) => audio.setVolume('music', v),
|
|
539
|
+
});
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
`onUpdate` fires continuously during drag, `onChange` fires once when drag ends.
|
|
543
|
+
|
|
544
|
+
### Toggle
|
|
545
|
+
|
|
546
|
+
Two-state toggle switch with animation:
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
// Graphics-based
|
|
550
|
+
const mute = new Toggle({
|
|
551
|
+
value: false,
|
|
552
|
+
width: 52, height: 28,
|
|
553
|
+
onColor: 0x22cc22, offColor: 0x666666,
|
|
554
|
+
onChange: (on) => audio.muteAll(!on),
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
// Asset-based (custom ON/OFF views with crossfade)
|
|
558
|
+
const ante = new Toggle({
|
|
559
|
+
value: false,
|
|
560
|
+
onView: 'toggle-on',
|
|
561
|
+
offView: 'toggle-off',
|
|
562
|
+
onChange: (on) => setAnteBet(on),
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// Programmatic control
|
|
566
|
+
mute.forceSwitch(true);
|
|
567
|
+
```
|
|
568
|
+
|
|
497
569
|
### ScrollContainer
|
|
498
570
|
|
|
499
571
|
Touch/drag scrolling with mouse wheel, inertia, and optional visual scrollbar:
|
|
@@ -824,6 +896,21 @@ All engine UI components are config-based: the reconciler passes JSX props as a
|
|
|
824
896
|
<label text="Item 1" />
|
|
825
897
|
<label text="Item 2" />
|
|
826
898
|
</scrollContainer>
|
|
899
|
+
|
|
900
|
+
{/* Slider */}
|
|
901
|
+
<slider min={0} max={1} value={volume} width={200} height={8}
|
|
902
|
+
fillColor={0xffd700} onUpdate={setVolume} />
|
|
903
|
+
|
|
904
|
+
{/* Toggle */}
|
|
905
|
+
<toggle value={isMuted} onColor={0x22cc22} onChange={setIsMuted} />
|
|
906
|
+
|
|
907
|
+
{/* Flex item props — work on any element inside <flexContainer> */}
|
|
908
|
+
<flexContainer direction="row" width={800} height={60}>
|
|
909
|
+
<graphics draw={drawBg} flexExclude /> {/* excluded from flow */}
|
|
910
|
+
<label text="Logo" flexShrink={0} /> {/* won't shrink */}
|
|
911
|
+
<container flexGrow={1} /> {/* fills remaining space */}
|
|
912
|
+
<button text="Menu" alignSelf="center" /> {/* centered on cross-axis */}
|
|
913
|
+
</flexContainer>
|
|
827
914
|
```
|
|
828
915
|
|
|
829
916
|
**Dash-notation** for nested config: `colors-default={0xff0000}` → `{ colors: { default: 0xff0000 } }`, `style-fontSize={24}` → `{ style: { fontSize: 24 } }`.
|
package/bin/simulate.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env npx tsx
|
|
2
2
|
import { SimulationRunner, formatSimulationResult } from '../src/lua/SimulationRunner';
|
|
3
3
|
import { ParallelSimulationRunner } from '../src/lua/ParallelSimulationRunner';
|
|
4
|
+
import { NativeSimulationRunner, findNativeBinary, formatNativeResult } from '../src/lua/NativeSimulationRunner';
|
|
4
5
|
import { cpus } from 'os';
|
|
5
|
-
import { resolve } from 'path';
|
|
6
|
+
import { resolve, dirname } from 'path';
|
|
6
7
|
|
|
7
8
|
// ─── Argument Parsing ───────────────────────────────────
|
|
8
9
|
|
|
@@ -10,9 +11,14 @@ function parseArgs(argv: string[]): Record<string, string> {
|
|
|
10
11
|
const args: Record<string, string> = {};
|
|
11
12
|
for (let i = 2; i < argv.length; i++) {
|
|
12
13
|
const arg = argv[i];
|
|
13
|
-
if (arg.startsWith('--')
|
|
14
|
+
if (arg.startsWith('--')) {
|
|
14
15
|
const key = arg.slice(2);
|
|
15
|
-
|
|
16
|
+
// Boolean flags (no value)
|
|
17
|
+
if (key === 'native' || key === 'js') {
|
|
18
|
+
args[key] = 'true';
|
|
19
|
+
} else if (i + 1 < argv.length) {
|
|
20
|
+
args[key] = argv[++i];
|
|
21
|
+
}
|
|
16
22
|
}
|
|
17
23
|
}
|
|
18
24
|
return args;
|
|
@@ -28,6 +34,8 @@ async function main() {
|
|
|
28
34
|
const action = args.action ?? 'spin';
|
|
29
35
|
const params = args.params ? JSON.parse(args.params) : undefined;
|
|
30
36
|
const workers = args.workers ? parseInt(args.workers, 10) : cpus().length;
|
|
37
|
+
const useNative = args.native === 'true';
|
|
38
|
+
const useJs = args.js === 'true';
|
|
31
39
|
|
|
32
40
|
// Load dev config
|
|
33
41
|
let config: any;
|
|
@@ -52,14 +60,46 @@ async function main() {
|
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
const gameId = config.gameDefinition.id ?? 'unknown';
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
|
|
64
|
+
// ─── Native binary detection ────────────────────────────
|
|
65
|
+
// Search in config dir first, then in the game-engine package root
|
|
66
|
+
const engineRoot = resolve(__dirname, '..');
|
|
67
|
+
const binaryPath = args.binary ?? (useJs ? null : (findNativeBinary(dirname(configPath)) ?? findNativeBinary(engineRoot)));
|
|
68
|
+
|
|
69
|
+
if (useNative && !binaryPath) {
|
|
70
|
+
console.error('Native simulation binary not found.');
|
|
71
|
+
console.error('Use --binary <path> or set SIMULATE_BINARY environment variable.');
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
57
74
|
|
|
58
75
|
const onProgress = (completed: number, total: number) => {
|
|
59
76
|
const pct = Math.round((completed / total) * 100);
|
|
60
77
|
console.log(`Progress: ${completed.toLocaleString()}/${total.toLocaleString()} (${pct}%)`);
|
|
61
78
|
};
|
|
62
79
|
|
|
80
|
+
// ─── Native binary path ─────────────────────────────────
|
|
81
|
+
if (binaryPath) {
|
|
82
|
+
console.log(`Using native binary: ${binaryPath}`);
|
|
83
|
+
console.log(`Starting simulation for ${gameId} (${iterations.toLocaleString()} iterations, action: ${action})...`);
|
|
84
|
+
|
|
85
|
+
const runner = new NativeSimulationRunner({
|
|
86
|
+
binaryPath,
|
|
87
|
+
script: config.luaScript,
|
|
88
|
+
gameDefinition: config.gameDefinition,
|
|
89
|
+
iterations,
|
|
90
|
+
bet,
|
|
91
|
+
action,
|
|
92
|
+
params,
|
|
93
|
+
});
|
|
94
|
+
const result = await runner.run();
|
|
95
|
+
console.log(formatNativeResult(result));
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ─── JS simulation path ─────────────────────────────────
|
|
100
|
+
const useParallel = workers > 1;
|
|
101
|
+
console.log(`Starting simulation for ${gameId} (${iterations.toLocaleString()} iterations, action: ${action}, workers: ${useParallel ? workers : 1})...`);
|
|
102
|
+
|
|
63
103
|
let result;
|
|
64
104
|
|
|
65
105
|
if (useParallel) {
|
package/dist/index.cjs.js
CHANGED
|
@@ -2966,6 +2966,37 @@ function layoutLine(items, isRow, mainSize, justify, align, gap, crossOffset, cr
|
|
|
2966
2966
|
}
|
|
2967
2967
|
}
|
|
2968
2968
|
}
|
|
2969
|
+
// Shrink: if content overflows and mainSize is finite, shrink eligible items
|
|
2970
|
+
if (totalGrow === 0 && mainSize > 0) {
|
|
2971
|
+
const overflow = totalFixed + totalGap - mainSize;
|
|
2972
|
+
if (overflow > 0) {
|
|
2973
|
+
let totalShrinkable = 0;
|
|
2974
|
+
for (const item of items) {
|
|
2975
|
+
const shrink = item.child._flexConfig?.flexShrink ?? 1;
|
|
2976
|
+
if (shrink > 0) {
|
|
2977
|
+
totalShrinkable += isRow ? item.w : item.h;
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
if (totalShrinkable > 0) {
|
|
2981
|
+
for (const item of items) {
|
|
2982
|
+
const shrink = item.child._flexConfig?.flexShrink ?? 1;
|
|
2983
|
+
if (shrink > 0) {
|
|
2984
|
+
const itemMain = isRow ? item.w : item.h;
|
|
2985
|
+
const reduction = overflow * (itemMain / totalShrinkable);
|
|
2986
|
+
const newSize = Math.max(0, itemMain - reduction);
|
|
2987
|
+
if (isRow) {
|
|
2988
|
+
item.w = newSize;
|
|
2989
|
+
item.child.width = newSize;
|
|
2990
|
+
}
|
|
2991
|
+
else {
|
|
2992
|
+
item.h = newSize;
|
|
2993
|
+
item.child.height = newSize;
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
2969
3000
|
// Calculate total main size after flex
|
|
2970
3001
|
let totalMain = totalGap;
|
|
2971
3002
|
for (const item of items) {
|
|
@@ -3002,9 +3033,12 @@ function layoutLine(items, isRow, mainSize, justify, align, gap, crossOffset, cr
|
|
|
3002
3033
|
for (const item of items) {
|
|
3003
3034
|
const mainDim = isRow ? item.w : item.h;
|
|
3004
3035
|
const crossDim = isRow ? item.h : item.w;
|
|
3005
|
-
// Cross-axis alignment
|
|
3036
|
+
// Cross-axis alignment (alignSelf overrides align)
|
|
3037
|
+
const effectiveAlign = (item.child._flexConfig?.alignSelf && item.child._flexConfig.alignSelf !== 'auto')
|
|
3038
|
+
? item.child._flexConfig.alignSelf
|
|
3039
|
+
: align;
|
|
3006
3040
|
let crossPos = crossOffset;
|
|
3007
|
-
switch (
|
|
3041
|
+
switch (effectiveAlign) {
|
|
3008
3042
|
case 'start':
|
|
3009
3043
|
break;
|
|
3010
3044
|
case 'center':
|
|
@@ -3188,11 +3222,14 @@ class FlexContainer extends pixi_js.Container {
|
|
|
3188
3222
|
const contentH = this._explicitHeight > 0 ? this._explicitHeight - pt - pb : Infinity;
|
|
3189
3223
|
const mainLimit = isRow ? contentW : contentH;
|
|
3190
3224
|
const crossLimit = isRow ? contentH : contentW;
|
|
3191
|
-
// Measure children
|
|
3192
|
-
const measured =
|
|
3225
|
+
// Measure children (skip flexExclude — they position themselves)
|
|
3226
|
+
const measured = [];
|
|
3227
|
+
for (const child of this._layoutChildren) {
|
|
3228
|
+
if (child._flexConfig?.flexExclude)
|
|
3229
|
+
continue;
|
|
3193
3230
|
const { w, h, ox, oy } = measureChild(child);
|
|
3194
|
-
|
|
3195
|
-
}
|
|
3231
|
+
measured.push({ child, w, h, ox, oy });
|
|
3232
|
+
}
|
|
3196
3233
|
// Split into lines (if wrapping)
|
|
3197
3234
|
const lines = [];
|
|
3198
3235
|
if (flexWrap && mainLimit < Infinity) {
|
|
@@ -3235,7 +3272,12 @@ class FlexContainer extends pixi_js.Container {
|
|
|
3235
3272
|
const mainStart = isRow ? pl : pt;
|
|
3236
3273
|
// Offset items by padding
|
|
3237
3274
|
const tempItems = line.map((item) => ({ ...item }));
|
|
3238
|
-
|
|
3275
|
+
// For single-line layouts, use the full available cross space for alignment;
|
|
3276
|
+
// for multi-line (wrapping), each line gets its own measured cross size.
|
|
3277
|
+
const effectiveCross = lines.length === 1 && crossLimit < Infinity
|
|
3278
|
+
? crossLimit
|
|
3279
|
+
: (crossLimit < Infinity ? Math.min(lineCross, crossLimit) : lineCross);
|
|
3280
|
+
layoutLine(tempItems, isRow, mainLimit < Infinity ? mainLimit : 0, mainLimit < Infinity ? justifyContent : 'start', alignItems, gap, crossOffset, effectiveCross);
|
|
3239
3281
|
// Apply main-axis padding offset
|
|
3240
3282
|
for (const item of tempItems) {
|
|
3241
3283
|
const origChild = line.find((l) => l.child === item.child);
|