@hology/core 0.0.262 → 0.0.263
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/gameplay/input/input.d.ts +1 -0
- package/dist/gameplay/input/input.js +1 -1
- package/dist/scene/assets-provider.d.ts +1 -0
- package/dist/scene/assets-provider.js +1 -1
- package/dist/scene/materializer.js +1 -1
- package/dist/scene/objects/data-asset.d.ts +6 -1
- package/dist/scene/objects/data-asset.js +1 -1
- package/dist/scene/runtime-asset-service.d.ts +5 -1
- package/dist/scene/runtime-asset-service.js +1 -1
- package/dist/scene/storage/packed-json.js +1 -1
- package/dist/test/data-asset-definition.test.js +1 -1
- package/dist/test/input.test.js +1 -1
- package/dist/test/packed-json.test.js +1 -1
- package/dist/test/runtime-asset-service.test.js +1 -1
- package/package.json +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { Asset } from '../model.js';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
2
3
|
export declare class DataAssetRef<T = unknown> {
|
|
3
4
|
readonly asset: Asset;
|
|
4
5
|
readonly value: T;
|
|
6
|
+
/** Fresh snapshots on saved changes. Unsubscribe when the consumer is destroyed. */
|
|
7
|
+
readonly onChange: Observable<DataAssetRef<T>>;
|
|
5
8
|
readonly id: string;
|
|
6
9
|
readonly name: string;
|
|
7
10
|
readonly definitionId: string;
|
|
8
|
-
constructor(asset: Asset, value: T
|
|
11
|
+
constructor(asset: Asset, value: T,
|
|
12
|
+
/** Fresh snapshots on saved changes. Unsubscribe when the consumer is destroyed. */
|
|
13
|
+
onChange?: Observable<DataAssetRef<T>>);
|
|
9
14
|
}
|
|
10
15
|
//# sourceMappingURL=data-asset.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export class DataAssetRef{constructor(t
|
|
1
|
+
import{EMPTY as t}from"rxjs";export class DataAssetRef{constructor(s,i,e=t){this.asset=s,this.value=i,this.onChange=e,this.id=s.id,this.name=s.name,this.definitionId=s.dataAsset?.definition??""}}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -10,14 +10,18 @@ export declare class RuntimeAssetsService extends AssetsProvider {
|
|
|
10
10
|
private readonly liveUpdateQueues;
|
|
11
11
|
private readonly hot;
|
|
12
12
|
private readonly livePrefabParametersHandler;
|
|
13
|
+
private readonly liveDataAssetHandler;
|
|
14
|
+
readonly supportsLiveDataAssets: boolean;
|
|
13
15
|
readonly supportsLivePrefabParameters: boolean;
|
|
14
16
|
onCreate: Subject<Asset>;
|
|
15
17
|
onDelete: Subject<Asset>;
|
|
16
18
|
onUpdate: Subject<Asset>;
|
|
17
|
-
constructor(backend: RuntimeBackendService, livePrefabParametersEnabled?: boolean);
|
|
19
|
+
constructor(backend: RuntimeBackendService, livePrefabParametersEnabled?: boolean, liveDataAssetsEnabled?: boolean);
|
|
18
20
|
getAssets(): Promise<Asset[]>;
|
|
19
21
|
getAsset(id: string): Promise<Asset>;
|
|
20
22
|
private loadAssets;
|
|
23
|
+
/** Updates the catalog; subscribed consumers decide how to apply the change. */
|
|
24
|
+
applyLiveDataAsset(asset: Asset): Promise<boolean>;
|
|
21
25
|
dispose(): void;
|
|
22
26
|
private queueLivePrefabParameters;
|
|
23
27
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{AssetsProvider as e}from"../scene/assets-provider.js";import{Subject as t}from"rxjs";export class RuntimeAssetsService extends e{constructor(e,s=!0){super(),this.backend=e,this.assets=new Map,this.loaded=!1,this.loadingAssets=null,this.liveUpdateQueues=new Map,this.hot=import.meta.hot,this.livePrefabParametersHandler=e=>{(function(e){const t=e;return 1===t?.version&&"prefab"===t.asset?.type&&null!=t.asset.prefab})(e)&&this.queueLivePrefabParameters(e.asset)},this.onCreate=new t,this.onDelete=new t,this.onUpdate=new t,this.supportsLivePrefabParameters=s&&null!=this.hot,this.supportsLivePrefabParameters&&this.hot.on("hology:prefab-parameters",this.livePrefabParametersHandler)}async getAssets(){return this.loaded||(this.loadingAssets??(this.loadingAssets=this.loadAssets()),await this.loadingAssets),Array.from(this.assets.values())}async getAsset(e){const t=this.assets.get(e);if(null!=t)return t;const s=await async function(e,t){r.has(e)||r.set(e,t(e));return r.get(e)}(e,()=>this.backend.getAsset(e));return null!=s&&this.assets.set(e,s),s}async loadAssets(){try{const e=await this.backend.getAssets();for(const t of e)this.assets.set(t.id,t);return this.loaded=!0,e}finally{this.loadingAssets=null}}async applyLivePrefabParameters(e){if("prefab"!==e?.type||null==e.prefab)return!1;await this.getAssets();const t=this.assets.get(e.id);if("prefab"!==t?.type||null==t.prefab)return console.warn(`Unable to apply live prefab parameters for ${e.name??e.id}: the prefab is not loaded.`),!1;const a=function(e,t){if(e.id!==t.id||"prefab"!==e.type||"prefab"!==t.type||null==e.prefab||null==t.prefab||e.prefab.mainActorId!==t.prefab.mainActorId)return null;const a=s(e.prefab.objects,t.prefab.objects);if(null==a)return null;return{...e,prefab:{...e.prefab,objects:a}}}(t,e);return null==a?(console.warn(`Unable to apply live prefab parameters for ${e.name??e.id}: its structure changed. Respawn or restart to use the saved structure.`),!1):(JSON.stringify(t.prefab.eventGraph)!==JSON.stringify(e.prefab.eventGraph)&&console.warn(`Live event graph updates are not supported for prefab ${e.name??e.id}. The running graph was preserved.`),this.assets.set(a.id,a),this.onUpdate.next(a),!0)}dispose(){this.supportsLivePrefabParameters&&this.hot.off("hology:prefab-parameters",this.livePrefabParametersHandler),this.liveUpdateQueues.clear()}queueLivePrefabParameters(e){const t=(this.liveUpdateQueues.get(e.id)??Promise.resolve()).catch(()=>{}).then(()=>this.applyLivePrefabParameters(e)).then(()=>{},t=>{console.warn(`Unable to apply live prefab parameters for ${e.name??e.id}.`,t)});this.liveUpdateQueues.set(e.id,t),t.finally(()=>{this.liveUpdateQueues.get(e.id)===t&&this.liveUpdateQueues.delete(e.id)})}}function s(e,t){if(e.length!==t.length)return null;const r=[];for(let n=0;n<e.length;n++){const i=e[n],o=t[n];if(null==i||null==o||i.id!==o.id||i.type!==o.type||"prefab"===i.type&&i.assetId!==o.assetId)return null;const l=s(i.children??[],o.children??[]);if(null==l)return null;let p={...i,children:null!=i.children||null!=o.children?l:void 0};if("actor"===i.type){if(i.actor?.type!==o.actor?.type||!a(i.actor?.components,o.actor?.components))return null;const e=i.actor?.components??[],t=o.actor?.components??[];p={...p,actor:{...i.actor,params:o.actor?.params,innerParams:o.actor?.innerParams,components:e.map((e,s)=>({...e,params:t[s]?.params,innerParams:t[s]?.innerParams}))}}}else"prefab"===i.type&&(p={...p,prefab:{...i.prefab,params:o.prefab?.params,innerParams:o.prefab?.innerParams}});r.push(p)}return r}function a(e,t){const s=e??[],a=t??[];return s.length===a.length&&s.every((e,t)=>e.type===a[t]?.type)}const r=new Map;/*
|
|
1
|
+
import{AssetsProvider as e}from"../scene/assets-provider.js";import{Subject as t}from"rxjs";export class RuntimeAssetsService extends e{constructor(e,s=!0,a=!0){super(),this.backend=e,this.assets=new Map,this.loaded=!1,this.loadingAssets=null,this.liveUpdateQueues=new Map,this.hot=import.meta.hot,this.livePrefabParametersHandler=e=>{(function(e){const t=e;return 1===t?.version&&"prefab"===t.asset?.type&&null!=t.asset.prefab})(e)&&this.queueLivePrefabParameters(e.asset)},this.liveDataAssetHandler=e=>{const t=e;1===t?.version&&"data"===t.asset?.type&&"string"==typeof t.asset.id&&null!=t.asset.dataAsset&&this.applyLiveDataAsset(t.asset).catch(e=>console.warn("Unable to reload data asset",e))},this.supportsLiveDataAssets=!1,this.supportsLivePrefabParameters=!1,this.onCreate=new t,this.onDelete=new t,this.onUpdate=new t,this.supportsLiveDataAssets=a&&null!=this.hot,this.supportsLiveDataAssets&&this.hot.on("hology:data-asset",this.liveDataAssetHandler),this.supportsLivePrefabParameters=s&&null!=this.hot,this.supportsLivePrefabParameters&&this.hot.on("hology:prefab-parameters",this.livePrefabParametersHandler)}async getAssets(){return this.loaded||(this.loadingAssets??(this.loadingAssets=this.loadAssets()),await this.loadingAssets),Array.from(this.assets.values())}async getAsset(e){const t=this.assets.get(e);if(null!=t)return t;const s=await async function(e,t){r.has(e)||r.set(e,t(e));return r.get(e)}(e,()=>this.backend.getAsset(e));return null!=s&&this.assets.set(e,s),s}async loadAssets(){try{const e=await this.backend.getAssets();for(const t of e)this.assets.set(t.id,t);return this.loaded=!0,e}finally{this.loadingAssets=null}}async applyLivePrefabParameters(e){if("prefab"!==e?.type||null==e.prefab)return!1;await this.getAssets();const t=this.assets.get(e.id);if("prefab"!==t?.type||null==t.prefab)return console.warn(`Unable to apply live prefab parameters for ${e.name??e.id}: the prefab is not loaded.`),!1;const a=function(e,t){if(e.id!==t.id||"prefab"!==e.type||"prefab"!==t.type||null==e.prefab||null==t.prefab||e.prefab.mainActorId!==t.prefab.mainActorId)return null;const a=s(e.prefab.objects,t.prefab.objects);if(null==a)return null;return{...e,prefab:{...e.prefab,objects:a}}}(t,e);return null==a?(console.warn(`Unable to apply live prefab parameters for ${e.name??e.id}: its structure changed. Respawn or restart to use the saved structure.`),!1):(JSON.stringify(t.prefab.eventGraph)!==JSON.stringify(e.prefab.eventGraph)&&console.warn(`Live event graph updates are not supported for prefab ${e.name??e.id}. The running graph was preserved.`),this.assets.set(a.id,a),this.onUpdate.next(a),!0)}async applyLiveDataAsset(e){if("data"!==e?.type||null==e.dataAsset)return!1;await this.getAssets();const t=this.assets.get(e.id);return null!=t&&t.dataAsset?.definition!==e.dataAsset.definition?(console.warn(`Data asset ${e.name}: changing definition type requires a restart.`),!1):(this.assets.set(e.id,e),this.onUpdate.next(e),!0)}dispose(){this.supportsLiveDataAssets&&this.hot.off("hology:data-asset",this.liveDataAssetHandler),this.supportsLivePrefabParameters&&this.hot.off("hology:prefab-parameters",this.livePrefabParametersHandler),this.liveUpdateQueues.clear()}queueLivePrefabParameters(e){const t=(this.liveUpdateQueues.get(e.id)??Promise.resolve()).catch(()=>{}).then(()=>this.applyLivePrefabParameters(e)).then(()=>{},t=>{console.warn(`Unable to apply live prefab parameters for ${e.name??e.id}.`,t)});this.liveUpdateQueues.set(e.id,t),t.finally(()=>{this.liveUpdateQueues.get(e.id)===t&&this.liveUpdateQueues.delete(e.id)})}}function s(e,t){if(e.length!==t.length)return null;const r=[];for(let n=0;n<e.length;n++){const i=e[n],o=t[n];if(null==i||null==o||i.id!==o.id||i.type!==o.type||"prefab"===i.type&&i.assetId!==o.assetId)return null;const l=s(i.children??[],o.children??[]);if(null==l)return null;let p={...i,children:null!=i.children||null!=o.children?l:void 0};if("actor"===i.type){if(i.actor?.type!==o.actor?.type||!a(i.actor?.components,o.actor?.components))return null;const e=i.actor?.components??[],t=o.actor?.components??[];p={...p,actor:{...i.actor,params:o.actor?.params,innerParams:o.actor?.innerParams,components:e.map((e,s)=>({...e,params:t[s]?.params,innerParams:t[s]?.innerParams}))}}}else"prefab"===i.type&&(p={...p,prefab:{...i.prefab,params:o.prefab?.params,innerParams:o.prefab?.innerParams}});r.push(p)}return r}function a(e,t){const s=e??[],a=t??[];return s.length===a.length&&s.every((e,t)=>e.type===a[t]?.type)}const r=new Map;/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{gzipSync as r,gunzipSync as e,strFromU8 as t,strToU8 as n}from"fflate";const o="@hology/packed-json:1:gzip:base64:";export function serializeStorageJson(r){return JSON.stringify(a(r),null,2)}export function parseStorageJson(r){return JSON.parse(r,(r,e)=>f(e))}export function decodeStorageValue(r){const e=f(r);return e!==r?decodeStorageValue(e):Array.isArray(r)?r.map(r=>decodeStorageValue(r)):y(r)?Object.fromEntries(Object.entries(r).map(([r,e])=>[r,decodeStorageValue(e)])):r}function a(r){if(Array.isArray(r))return r.map(a);if(!y(r))return r;const e={};for(const[t,n]of Object.entries(r))"vertexMaterials"===t&&Array.isArray(n)?e[t]=l(n):"landscape"===t&&y(n)?e[t]=s(n):"paintedScatter"===t&&y(n)?e[t]=i(n):"secondaryMotion"===t&&y(n)?e[t]=c(n):e[t]=a(n);return e}function s(r){const e=u(r);return Array.isArray(r.heightMaps)&&(e.heightMaps=l(r.heightMaps)),Array.isArray(r.holes)&&(e.holes=l(r.holes)),e}function i(r){const e=u(r);return Array.isArray(r.cells)&&(e.cells=r.cells.map(r=>{if(!y(r))return a(r);const e=u(r);return Array.isArray(r.instances)&&(e.instances=l(r.instances)),e})),e}function c(r){const e=u(r);return Array.isArray(r.regions)&&(e.regions=r.regions.map(r=>{if(!y(r))return a(r);const e=u(r);return Array.isArray(r.paintedVertices)&&(e.paintedVertices=l(r.paintedVertices)),e})),e}function u(r){return Object.fromEntries(Object.entries(r).map(([r,e])=>[r,a(e)]))}function l(e){const
|
|
1
|
+
import{gzipSync as r,gunzipSync as e,strFromU8 as t,strToU8 as n}from"fflate";const o="@hology/packed-json:1:gzip:base64:";export function serializeStorageJson(r){return JSON.stringify(a(r),null,2)}export function parseStorageJson(r){return JSON.parse(r,(r,e)=>f(e))}export function decodeStorageValue(r){const e=f(r);return e!==r?decodeStorageValue(e):Array.isArray(r)?r.map(r=>decodeStorageValue(r)):y(r)?Object.fromEntries(Object.entries(r).map(([r,e])=>[r,decodeStorageValue(e)])):r}function a(r){if(Array.isArray(r))return r.map(a);if(!y(r))return r;const e={};for(const[t,n]of Object.entries(r))"vertexMaterials"===t&&Array.isArray(n)?e[t]=l(n):"latticeDeformation"===t&&y(n)?e[t]=l(n,0):"landscape"===t&&y(n)?e[t]=s(n):"paintedScatter"===t&&y(n)?e[t]=i(n):"secondaryMotion"===t&&y(n)?e[t]=c(n):e[t]=a(n);return e}function s(r){const e=u(r);return Array.isArray(r.heightMaps)&&(e.heightMaps=l(r.heightMaps)),Array.isArray(r.holes)&&(e.holes=l(r.holes)),e}function i(r){const e=u(r);return Array.isArray(r.cells)&&(e.cells=r.cells.map(r=>{if(!y(r))return a(r);const e=u(r);return Array.isArray(r.instances)&&(e.instances=l(r.instances)),e})),e}function c(r){const e=u(r);return Array.isArray(r.regions)&&(e.regions=r.regions.map(r=>{if(!y(r))return a(r);const e=u(r);return Array.isArray(r.paintedVertices)&&(e.paintedVertices=l(r.paintedVertices)),e})),e}function u(r){return Object.fromEntries(Object.entries(r).map(([r,e])=>[r,a(e)]))}function l(e,t=128){const s=JSON.stringify(e);return s.length<t?a(e):o+function(r){let e="";const t=32768;for(let n=0;n<r.length;n+=t)e+=String.fromCharCode(...r.subarray(n,n+t));return btoa(e)}(r(n(s),{level:9,mtime:0}))}function f(r){if("string"!=typeof r||!r.startsWith(o))return r;const n=r.slice(34);try{return JSON.parse(t(e(function(r){const e=atob(r),t=new Uint8Array(e.length);for(let r=0;r<e.length;r++)t[r]=e.charCodeAt(r);return t}(n))))}catch(r){const e=r instanceof Error?`${r.name}: ${r.message}`:String(r);throw Object.assign(new Error(`Failed to decode packed Hology storage data: ${e}`),{cause:r})}}function y(r){return null!=r&&"object"==typeof r&&!Array.isArray(r)}/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{__decorate as e,__metadata as t}from"tslib";import{describe as a,expect as s,it as i,vi as o}from"vitest";o.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=("string"!=typeof t||!t.startsWith("is"))&&o.fn()),e[t]),set:(e,t,a)=>(e[t]=a,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e}),"undefined"==typeof AudioBuffer&&Object.defineProperty(globalThis,"AudioBuffer",{configurable:!0,value:class{}})}),o.mock("../gameplay/actors/builtin/index.js",()=>({default:{}})),o.mock("../effects/vfx/vfx-actor.js",()=>({VfxActor:class{}})),o.mock("../effects/vfx/vfx-param.js",()=>({VisualEffect:class{}})),o.mock("../rendering.js",()=>({RenderingView:class{},setRenderingPaused:o.fn()}));import{prepareClassParameters as n,prepareCustomParamsFromType as r,materializeDataAssetRef as d}from"../scene/materializer.js";import{SerializedParamType as m}from"../scene/model.js";import{DataAssetRef as
|
|
1
|
+
import{__decorate as e,__metadata as t}from"tslib";import{describe as a,expect as s,it as i,vi as o}from"vitest";o.hoisted(()=>{if("undefined"==typeof HTMLCanvasElement)return;const e=new Proxy({},{get:(e,t)=>(t in e||(e[t]=("string"!=typeof t||!t.startsWith("is"))&&o.fn()),e[t]),set:(e,t,a)=>(e[t]=a,!0)});Object.defineProperty(HTMLCanvasElement.prototype,"getContext",{configurable:!0,value:()=>e}),"undefined"==typeof AudioBuffer&&Object.defineProperty(globalThis,"AudioBuffer",{configurable:!0,value:class{}})}),o.mock("../gameplay/actors/builtin/index.js",()=>({default:{}})),o.mock("../effects/vfx/vfx-actor.js",()=>({VfxActor:class{}})),o.mock("../effects/vfx/vfx-param.js",()=>({VisualEffect:class{}})),o.mock("../rendering.js",()=>({RenderingView:class{},setRenderingPaused:o.fn()}));import{prepareClassParameters as n,prepareCustomParamsFromType as r,materializeDataAssetRef as d}from"../scene/materializer.js";import{SerializedParamType as m}from"../scene/model.js";import{DataAssetRef as p}from"../scene/objects/data-asset.js";import{Subject as l}from"rxjs";import{DataAssetDefinition as c,getAssignableDataAssetDefinitions as u,getDataAssetDefinitionId as f,getDataAssetDefinitions as y,getDataAssetDefinitionType as v,isDataAssetDefinitionAbstract as g,Parameter as B,ParameterDefinition as b}from"../shader/parameter.js";let w=class{constructor(){this.amount=1}};e([B({type:Number}),t("design:type",Object)],w.prototype,"amount",void 0),w=e([b("test.data.statModifier")],w);let A=class{constructor(){this.displayName="Item"}};e([B({type:String}),t("design:type",Object)],A.prototype,"displayName",void 0),A=e([c("test.data.item",{abstract:!0})],A);let h=class extends A{constructor(){super(...arguments),this.damage=5,this.modifier=new w}};e([B({type:Number}),t("design:type",Object)],h.prototype,"damage",void 0),e([B({type:()=>w}),t("design:type",Object)],h.prototype,"modifier",void 0),h=e([c("test.data.weapon")],h);let N=class extends A{constructor(){super(...arguments),this.charges=1}};e([B({type:Number}),t("design:type",Object)],N.prototype,"charges",void 0),N=e([c("test.data.consumable")],N);class j{constructor(){this.item=null,this.items=[]}}e([B({type:()=>p,dataAssetOf:()=>A}),t("design:type",p)],j.prototype,"item",void 0),e([B({type:()=>p,dataAssetOf:()=>A,array:!0}),t("design:type",Array)],j.prototype,"items",void 0);class x{constructor(){this.items=[]}}function D(){return{id:"asset.weapon",name:"Bronze Sword",type:"data",dataAsset:{definition:"test.data.weapon",params:{displayName:{type:m.String,value:"Bronze Sword"},damage:{type:m.Number,value:12},modifier:{type:m.Struct,struct:"test.data.statModifier",value:{amount:{type:m.Number,value:3}}}}}}}function O(e){const t=new Map(e.map(e=>[e.id,e]));return{getAsset:o.fn(async e=>t.get(e)??null),getAssets:o.fn(async()=>e)}}e([B(),t("design:type",Array)],x.prototype,"items",void 0),Reflect.defineMetadata("design:type",Array,x.prototype,"items"),Reflect.defineMetadata("parameter:arrayElementType",p,x.prototype,"items"),Reflect.defineMetadata("parameter:dataAssetOf",A,x.prototype,"items"),a("data asset definitions",()=>{i("registers data asset definitions separately from parameter definitions",()=>{s(v("test.data.item")).toBe(A),s(f(h)).toBe("test.data.weapon"),s(g(A)).toBe(!0),s(g("test.data.item")).toBe(!0);const e=u(A);s(e.map(e=>e.id)).toEqual(["test.data.consumable","test.data.weapon"]);const t=y({includeAbstract:!0});s(t.some(e=>"test.data.item"===e.id)).toBe(!0),s(t.some(e=>"test.data.statModifier"===e.id)).toBe(!1),s(v("test.data.statModifier")).toBeUndefined()}),i("serializes data asset references as asset ids, including arrays",()=>{const e=r(j,{}),t=e.item,a=e.items;s(t.type).toBe(m.DataAsset),s(t.value).toBeNull(),s(a.type).toBe(m.Array),s(a.element).toBe(m.DataAsset),s(a.value).toEqual([])}),i("uses reflected metadata for data asset reference arrays",()=>{const e=r(x,{}).items;s(e.type).toBe(m.Array),s(e.element).toBe(m.DataAsset),s(e.value).toEqual([])}),i("materializes a data asset reference into a wrapper with nested inline structs",async()=>{const e=D(),t=O([e]),a=await n({item:{type:m.DataAsset,value:e.id},items:{type:m.Array,element:m.DataAsset,value:[e.id]}},j,t,{}),i=a.item;s(i).toBeInstanceOf(p),s(i.id).toBe(e.id),s(i.name).toBe("Bronze Sword"),s(i.asset).toBe(e),s(i.definitionId).toBe("test.data.weapon"),s(i.value).toBeInstanceOf(h),s(i.value.displayName).toBe("Bronze Sword"),s(i.value.damage).toBe(12),s(i.value.modifier).toBeInstanceOf(w),s(i.value.modifier.amount).toBe(3),s(a.items).toHaveLength(1),s(a.items[0].value.damage).toBe(12)}),i("reloads fresh snapshots, restores removed parameters to defaults, and unsubscribes",async()=>{let e=D();const t=new l,a={supportsLiveDataAssets:!0,onUpdate:t,getAsset:async()=>e},i=await d(e.id,a,{}),n=[],r=i.onChange.subscribe(e=>n.push(e));e={...e,dataAsset:{...e.dataAsset,params:{damage:{type:m.Number,value:25}}}},t.next(e),await o.waitFor(()=>s(n).toHaveLength(1)),s(n[0].value.damage).toBe(25),s(n[0].value.displayName).toBe((new h).displayName),s(i.value.damage).toBe(12),r.unsubscribe(),s(t.observers).toHaveLength(0),t.next(e),s(n).toHaveLength(1)}),i("resolves missing and invalid data asset references safely",async()=>{const e=o.spyOn(console,"warn").mockImplementation(()=>{}),t=O([{id:"asset.notData",name:"Texture pretending",type:"texture"},{id:"asset.unknownDefinition",name:"Unknown Definition",type:"data",dataAsset:{definition:"test.data.unknown",params:{}}},{id:"asset.abstractDefinition",name:"Abstract Definition",type:"data",dataAsset:{definition:"test.data.item",params:{}}},{id:"asset.invalidParams",name:"Invalid Params",type:"data",dataAsset:{definition:"test.data.weapon",params:"bad"}}]);await s(d("asset.missing",t,{})).resolves.toBeNull(),await s(d("asset.notData",t,{})).resolves.toBeNull(),await s(d("asset.unknownDefinition",t,{})).resolves.toBeNull(),await s(d("asset.abstractDefinition",t,{})).resolves.toBeNull(),await s(d("asset.invalidParams",t,{})).resolves.toBeNull();const a=await n({item:{type:m.DataAsset,value:"asset.missing"}},j,t,{});s(a.item).toBeUndefined(),s(e).toHaveBeenCalled(),e.mockRestore()})});/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
package/dist/test/input.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{expect as e,test as t,vi as a}from"vitest";import o from"typedi";import{ActionInput as n,ButtonInput as s,ToggleInput as p}from"../gameplay/input/input.js";a.mock("../gameplay/services/render.js",()=>({ViewController:class{}}));import{InputService as
|
|
1
|
+
import{expect as e,test as t,vi as a}from"vitest";import o from"typedi";import{ActionInput as n,ButtonInput as s,ToggleInput as p}from"../gameplay/input/input.js";a.mock("../gameplay/services/render.js",()=>({ViewController:class{}}));import{InputService as l}from"../gameplay/input/input-service.js";import{GamepadButtonBind as i,Keybind as d}from"../gameplay/input/keybind.js";import{ViewController as u}from"../gameplay/services/render.js";t("button input uses hold mode by default",()=>{const t=new s,a=[];t.onStart(()=>a.push(t.activated)),t.onEnd(()=>a.push(t.activated)),t.toggle(!0),e(t.activated).toBe(!0),t.toggle(!0),e(a).toEqual([!0]),t.toggle(!1),e(t.activated).toBe(!1),e(a).toEqual([!0,!1])}),t("removed start listeners do not run on subsequent presses",()=>{const t=new s,o=a.fn(),n=a.fn();t.onStart(o),t.onStart(n),t.removeOnStart(o),t.removeOnStart(o),t.toggle(!0),e(o).not.toHaveBeenCalled(),e(n).toHaveBeenCalledTimes(1)}),t("button input can switch to toggle mode at runtime",()=>{const t=new s,a=[];t.onStart(()=>a.push(t.activated)),t.onEnd(()=>a.push(t.activated)),t.mode="toggle",t.toggle(!0),e(t.activated).toBe(!1),t.toggle(!1),e(t.activated).toBe(!0),t.toggle(!1),e(t.activated).toBe(!1),e(a).toEqual([!0,!1])}),t("action and toggle inputs remain compatibility presets",()=>{const t=new n;t.toggle(!0),e(t.activated).toBe(!0),t.toggle(!1),e(t.activated).toBe(!1);const a=new p;a.toggle(!0),e(a.activated).toBe(!1),a.toggle(!1),e(a.activated).toBe(!0)}),t("held gamepad buttons can be suppressed until released",()=>{const t={pressed:!0,value:1},a={connected:!0,mapping:"standard",buttons:[t],axes:[]},o=function(){const e=Object.create(l.prototype);return Object.assign(e,{view:{paused:!1},inputs:new Map,deltaCallbacks:new Map,toggleCallbacks:new Map,keybinds:new Map,gamepadButtonBinds:new Map,gamepadAxisBinds:new Map,activeToggleCallbacks:[],prevGamepadButtonStates:new Map,suppressedGamepadButtons:new Map,lastActiveDevice:"kbm",onDeviceChanged:{next:()=>{}}}),e}(),n=[];o.gamepadButtonBinds.set("jump",[new i(0)]),o.toggleCallbacks.set("jump",e=>n.push(e)),Object.defineProperty(navigator,"getGamepads",{configurable:!0,value:()=>[a]}),o.suppressHeldGamepadButtonsUntilRelease(),o.update(0),t.pressed=!1,t.value=0,o.update(0),t.pressed=!0,t.value=1,o.update(0),e(n).toEqual([!0])}),t("mouse button gameplay input requires pointer lock",()=>{let t=null;Object.defineProperty(document,"pointerLockElement",{configurable:!0,get:()=>t}),o.set(u,{paused:!1,htmlElement:document.body});const a=new l,n=[];a.keybinds.set("attack",[new d("MouseLeft")]),a.toggleCallbacks.set("attack",e=>n.push(e)),a.onKeyDown({button:0}),t=document.body,a.onKeyDown({button:0}),e(n).toEqual([!0])});/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{describe as e,expect as
|
|
1
|
+
import{describe as e,expect as t,test as a,vi as s}from"vitest";import{decodeStorageValue as r,parseStorageJson as n,serializeStorageJson as o}from"../scene/storage/packed-json.js";const i="@hology/packed-json:1:gzip:base64:";e("packed storage JSON",()=>{a("packs dense scene fields and restores their original runtime shapes",()=>{const e=Array.from({length:100},(e,t)=>({i:t,y:Math.sin(t)})),a=Array.from({length:100},(e,t)=>({i:t,w:[0,.25,.75]})),s={id:"scene",data:[{id:"landscape",name:"Landscape",landscape:{options:{},heightMaps:[{x:0,y:0,points:e}],holes:a},vertexMaterials:a,paintedScatter:{palette:[],brush:{radius:1,density:1},cells:[{key:"0:0",revision:1,instances:Array.from({length:50},(e,t)=>({id:`instance-${t}`,paletteId:"trees",position:[t,0,t],rotation:[0,0,0],scale:[1,1,1]}))}]}}]},r=o(s),l=JSON.parse(r);t(l.data[0].landscape.heightMaps).toMatch(i),t(l.data[0].landscape.holes).toMatch(i),t(l.data[0].vertexMaterials).toMatch(i),t(l.data[0].paintedScatter.cells[0].instances).toMatch(i),t(n(r)).toEqual(s)}),a("packs secondary-motion paint and decodes already-parsed bundle data",()=>{const e={mesh:{secondaryMotion:{version:1,regions:[{id:"cloth",paintedVertices:Array.from({length:100},(e,t)=>[t,t/100])}]}}},a=JSON.parse(o(e));t(a.mesh.secondaryMotion.regions[0].paintedVertices).toMatch(i),t(r(a)).toEqual(e)}),a.each([1,2,8])("packs a lattice with %i segments onto one line and restores it",e=>{const a={enabled:!1,segments:[e,e,e],min:[0,0,0],max:[1,1,1],offsets:Array.from({length:(e+1)**3},(e,t)=>[0===t?-.125:0,0,0])},s={data:[{id:"mesh",latticeDeformation:a}]},l=o(s),c=JSON.parse(l);t(c.data[0].latticeDeformation).toMatch(i),t(l.split("\n").filter(e=>e.includes("latticeDeformation"))).toEqual([` "latticeDeformation": "${c.data[0].latticeDeformation}"`]),t(n(l)).toEqual(s),t(r(c)).toEqual(s),t(n(JSON.stringify(s))).toEqual(s),t(a.offsets).toHaveLength((e+1)**3)}),a("leaves small selected values readable",()=>{const e={vertexMaterials:[{i:2,w:[1,0,0]}]},a=o(e);t(JSON.parse(a).vertexMaterials).toEqual(e.vertexMaterials),t(n(a)).toEqual(e)}),a("leaves unrelated large arrays readable",()=>{const e={children:Array.from({length:100},(e,t)=>({id:`${t}`,name:`Object ${t}`}))},a=JSON.parse(o(e));t(a.children).toEqual(e.children)}),a("produces identical packed data at different save times",()=>{const e={latticeDeformation:{segments:[1,1,1],min:[0,0,0],max:[1,1,1],offsets:Array.from({length:8},()=>[0,0,0])},vertexMaterials:Array.from({length:100},(e,t)=>({i:t,w:[0,.25,.75]}))};s.useFakeTimers();try{s.setSystemTime(new Date("2026-01-01T00:00:00Z"));const a=o(e);s.setSystemTime(new Date("2026-08-21T12:34:56Z"));const r=o(e);t(r).toBe(a)}finally{s.useRealTimers()}})});/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{describe as e,expect as t,it as a,vi as
|
|
1
|
+
import{describe as e,expect as t,it as a,vi as s}from"vitest";import{RuntimeAssetsService as n}from"../scene/runtime-asset-service.js";function o(e){return{getAssets:s.fn(async()=>e),getAsset:s.fn(async t=>e.find(e=>e.id===t)),getScenes:s.fn(),getScene:s.fn(),getSceneData:s.fn()}}function i(e){return{id:"prefab-a",name:"Prefab A",type:"prefab",prefab:{mainActorId:"actor-a",objects:[{id:"actor-a",name:"Actor A",type:"actor",position:[1,2,3],rotation:[0,0,0],scale:[1,1,1],actor:{type:"TestActor",params:{label:{type:0,value:e}},components:[{type:"TestComponent",params:{label:{type:0,value:`${e}-component`}}}]}}]}}}e("RuntimeAssetsService",()=>{a("publishes saved data assets and rejects definition type changes",async()=>{const e={id:"data-a",name:"Data A",type:"data",dataAsset:{definition:"test.ability",params:{}}},a=new n(o([e]),!1),i=s.fn();a.onUpdate.subscribe(i);const r={...e,name:"Updated ability"};t(await a.applyLiveDataAsset(r)).toBe(!0),t(await a.getAsset(e.id)).toBe(r),t(i).toHaveBeenCalledWith(r);const c=s.spyOn(console,"warn").mockImplementation(()=>{});t(await a.applyLiveDataAsset({...r,dataAsset:{definition:"other.ability",params:{}}})).toBe(!1),t(await a.getAsset(e.id)).toBe(r),t(i).toHaveBeenCalledTimes(1),c.mockRestore()}),a("shares concurrent asset list loads and reuses the loaded cache",async()=>{const e=[{id:"mesh-a",name:"Mesh A",type:"mesh"},{id:"material-a",name:"Material A",type:"material"}],a={getAssets:s.fn(async()=>(await new Promise(e=>setTimeout(e,5)),e)),getAsset:s.fn(),getScenes:s.fn(),getScene:s.fn(),getSceneData:s.fn()},o=new n(a);await Promise.all([o.getAssets(),o.getAssets()]),await o.getAssets(),t(a.getAssets).toHaveBeenCalledTimes(1)}),a("merges only compatible prefab parameter data into the runtime asset",async()=>{const e=i("old-value"),a=o([e]),r=new n(a,!1),c=[];r.onUpdate.subscribe(e=>c.push(e));const p=i("new-value");p.prefab.objects[0].position=[99,99,99],p.prefab.eventGraph={nodes:[{id:"ignored"}]};const l=s.spyOn(console,"warn").mockImplementation(()=>{});t(await r.applyLivePrefabParameters(p)).toBe(!0);const m=await r.getAsset(e.id);t(m.prefab.objects[0].actor.params.label.value).toBe("new-value"),t(m.prefab.objects[0].actor.components[0].params.label.value).toBe("new-value-component"),t(m.prefab.objects[0].position).toEqual([1,2,3]),t(m.prefab.eventGraph).toBeUndefined(),t(c).toEqual([m]),t(l).toHaveBeenCalledWith(t.stringContaining("event graph updates are not supported")),l.mockRestore()}),a("rejects structural prefab changes without replacing the cached asset",async()=>{const e=i("old-value"),a=o([e]),r=new n(a,!1),c=i("new-value");c.prefab.objects[0].actor.components[0].type="OtherComponent";const p=s.spyOn(console,"warn").mockImplementation(()=>{});t(await r.applyLivePrefabParameters(c)).toBe(!1),t(await r.getAsset(e.id)).toBe(e),t(p).toHaveBeenCalledWith(t.stringContaining("structure changed")),p.mockRestore()})});/*
|
|
2
2
|
* Copyright (©) 2026 Hology Interactive AB. All rights reserved.
|
|
3
3
|
* See the LICENSE.md file for details.
|
|
4
4
|
*/
|