Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ module.exports = function (config) {
include: ['**/*.glsl'] // inline shader files
}),

babel({
exclude: ['node_modules/**', '*.json']
}),

// These are needed for jszip node-environment compatibility,
// previously provided by browserify
globals(),
builtins(),

babel({
exclude: ['node_modules/**', '*.json']
})
builtins()
]
},

Expand Down
103 changes: 103 additions & 0 deletions src/scene/globals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import log from '../utils/log';
import { getPropertyPathTarget } from '../utils/props';

// prefix used to identify global property references
const GLOBAL_PREFIX = 'global.';
const GLOBAL_PREFIX_LENGTH = GLOBAL_PREFIX.length;

// name of 'hidden' (non-enumerable) property used to track global property references on an object
const GLOBAL_REGISTRY = '__global_prop';

// Property name references a global property?
export function isGlobalReference (val) {
return val?.slice(0, GLOBAL_PREFIX_LENGTH) === GLOBAL_PREFIX;
}

// Has object property been substitued with a value from a global reference?
// Property provided as a single-depth string name, or nested path array (`a.b.c` => ['a', 'b', 'c'])
export function isGlobalSubstitution (object, prop_or_path) {
const path = Array.isArray(prop_or_path) ? prop_or_path : [prop_or_path];
const target = getPropertyPathTarget(object, path);
const prop = path[path.length - 1];
return target?.[GLOBAL_REGISTRY]?.[prop] !== undefined;
}

// Flatten nested global properties for simpler string look-ups
export function flattenGlobalProperties (obj, prefix = null, globals = {}) {
prefix = prefix ? (prefix + '.') : GLOBAL_PREFIX;

for (const p in obj) {
const key = prefix + p;
const val = obj[p];
globals[key] = val;

if (typeof val === 'object' && !Array.isArray(val)) {
flattenGlobalProperties(val, key, globals);
}
}
return globals;
}

// Find and apply new global properties (and re-apply old ones)
export function applyGlobalProperties (globals, obj, target, key) {
let prop;

// Check for previously applied global substitution
if (target?.[GLOBAL_REGISTRY]?.[key]) {
prop = target[GLOBAL_REGISTRY][key];
}
// Check string for new global substitution
else if (typeof obj === 'string' && obj.slice(0, GLOBAL_PREFIX_LENGTH) === GLOBAL_PREFIX) {
prop = obj;
}

// Found global property to substitute
if (prop) {
// Mark property as global substitution
if (target[GLOBAL_REGISTRY] == null) {
Object.defineProperty(target, GLOBAL_REGISTRY, { value: {} });
}
target[GLOBAL_REGISTRY][key] = prop;

// Get current global value
let val = globals[prop];
let stack;
while (typeof val === 'string' && val.slice(0, GLOBAL_PREFIX_LENGTH) === GLOBAL_PREFIX) {
// handle globals that refer to other globals, detecting any cyclical references
stack = stack || [prop];
if (stack.indexOf(val) > -1) {
log({ level: 'warn', once: true }, 'Global properties: cyclical reference detected', stack);
val = null;
break;
}
stack.push(val);
val = globals[val];
}

// Create getter/setter
Object.defineProperty(target, key, {
enumerable: true,
get: function () {
return val; // return substituted value
},
set: function (v) {
// clear the global substitution and remove the getter/setter
delete target[GLOBAL_REGISTRY][key];
delete target[key];
target[key] = v; // save the new value
}
});
}
// Loop through object keys or array indices
else if (Array.isArray(obj)) {
for (let p = 0; p < obj.length; p++) {
applyGlobalProperties(globals, obj[p], obj, p);
}
}
else if (typeof obj === 'object') {
for (const p in obj) {
applyGlobalProperties(globals, obj[p], obj, p);
}
}
return obj;
}
47 changes: 26 additions & 21 deletions src/scene/scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export default class Scene {
// Options:
// `base_path`: base URL against which scene resources should be resolved (useful for Play) (default nulll)
// `blocking`: should rendering block on scene load completion (default true)
load(config_source = null, options = {}) {
load (config_source = null, options = {}) {
if (this.initializing) {
return this.initializing;
}
Expand All @@ -140,8 +140,9 @@ export default class Scene {
this.createCanvas();
this.prev_textures = this.config && Object.keys(this.config.textures); // save textures from last scene
this.initializing = this.loadScene(config_source, options)
.then(() => this.createWorkers())
.then(() => {
.then(async ({ texture_nodes }) => {
await this.createWorkers();

// Clean up resources from prior scene
this.destroyFeatureSelection();
WorkerBroker.postMessage(this.workers, 'self.clearFunctionStringCache');
Expand All @@ -150,11 +151,17 @@ export default class Scene {
// which need to be serialized, while one loaded only from a URL does not.
const serialize_funcs = ((typeof this.config_source === 'object') || this.hasSubscribersFor('load'));

const updating = this.updateConfig({ serialize_funcs, normalize: false, loading: true, fade_in: true });
const updating = this.updateConfig({
texture_nodes,
serialize_funcs,
normalize: false,
loading: true,
fade_in: true });

if (options.blocking === true) {
return updating;
await updating;
}
}).then(() => {

this.freePreviousTextures();
this.updating--;
this.initializing = null;
Expand Down Expand Up @@ -980,7 +987,7 @@ export default class Scene {
Load (or reload) the scene config
@return {Promise}
*/
loadScene(config_source = null, { base_path, file_type } = {}) {
async loadScene(config_source = null, { base_path, file_type } = {}) {
this.config_source = config_source || this.config_source;

if (typeof this.config_source === 'string') {
Expand All @@ -994,11 +1001,13 @@ export default class Scene {
// TODO: schedule for deprecation
this.config_path = this.base_path;

return SceneLoader.loadScene(this.config_source, { path: this.base_path, type: file_type }).then(({config, bundle}) => {
this.config = config;
this.config_bundle = bundle;
return this.config;
});
const { config, bundle, texture_nodes } = await SceneLoader.loadScene(
this.config_source,
{ path: this.base_path, type: file_type });

this.config = config;
this.config_bundle = bundle;
return { texture_nodes }; // pass along texture nodes for resolution after global property subtistution
}

// Add source to a scene, arguments `name` and `config` need to be provided:
Expand Down Expand Up @@ -1217,22 +1226,18 @@ export default class Scene {

// Update scene config, and optionally rebuild geometry
// rebuild can be boolean, or an object containing rebuild options to passthrough
updateConfig({ loading = false, rebuild = true, serialize_funcs, normalize = true, fade_in = false } = {}) {
updateConfig({ loading = false, rebuild = true, serialize_funcs, texture_nodes = {}, normalize = true, fade_in = false } = {}) {
this.generation = ++Scene.generation;
this.updating++;

// Apply globals, finalize textures and other resource paths if needed
this.config = SceneLoader.applyGlobalProperties(this.config);
if (normalize) {
// normalize whole scene
SceneLoader.normalize(this.config, this.config_bundle);
// normalize whole scene if requested - usually when user is making run-time updates to scene
SceneLoader.normalize(this.config, this.config_bundle, texture_nodes);
}
else {
// special handling for shader uniforms that are globals
SceneLoader.hoistStyleShaderUniformTextures(this.config, this.config_bundle, { include_globals: true });
SceneLoader.hoistTextureNodes(this.config, this.config_bundle, texture_nodes);

// just normalize top-level textures - necessary for adding base path to globals
SceneLoader.normalizeTextures(this.config, this.config_bundle);
}
this.trigger(loading ? 'load' : 'update', { config: this.config });

this.style_manager.init();
Expand Down
13 changes: 3 additions & 10 deletions src/scene/scene_bundle.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Utils from '../utils/utils';
import * as URLs from '../utils/urls';
import { isGlobalReference } from './globals';

import JSZip from 'jszip';
import yaml from 'js-yaml';
Expand Down Expand Up @@ -52,7 +53,7 @@ export class SceneBundle {
}

urlFor(url) {
if (isGlobal(url)) {
if (isGlobalReference(url)) {
return url;
}

Expand Down Expand Up @@ -104,7 +105,7 @@ export class ZipSceneBundle extends SceneBundle {
}

urlFor(url) {
if (isGlobal(url)) {
if (isGlobalReference(url)) {
return url;
}

Expand Down Expand Up @@ -196,14 +197,6 @@ export function createSceneBundle(url, path, parent, type = null) {
return new SceneBundle(url, path, parent);
}

// References a global property?
export function isGlobal (val) {
if (val && val.slice(0, 7) === 'global.') {
return true;
}
return false;
}

function parseResource (body) {
// jsyaml 'json' option allows duplicate keys
// Keeping this for backwards compatibility, but should consider migrating to requiring
Expand Down
Loading