Skip to content

Commit

Permalink
Fixes #10
Browse files Browse the repository at this point in the history
  • Loading branch information
gre committed Jan 1, 2016
1 parent 0c90b45 commit be7bb3c
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 30 deletions.
2 changes: 1 addition & 1 deletion src/Node.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Node.isGLNode = true;
Node.displayName = "GL.Node";

Node.propTypes = {
shader: PropTypes.number.isRequired,
shader: PropTypes.any.isRequired,
uniforms: PropTypes.object,
children: PropTypes.node,
width: PropTypes.number,
Expand Down
178 changes: 160 additions & 18 deletions src/Shaders.js
Original file line number Diff line number Diff line change
@@ -1,46 +1,188 @@
const {EventEmitter} = require("events");
const invariant = require("invariant");

function defer () {
const deferred = {};
const promise = new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
});
deferred.promise = promise;
return deferred;
}

const INLINE_NAME = "<inline>";

let _uid = 1;
const names = {};
const shaders = {};
const names = {}; // keep names
const shaders = {}; // keep shader objects
const shadersCompileResponses = {}; // keep promise of compile responses
const shadersReferenceCounters = {}; // reference count the shaders created with Shaders.create()/used inline so we don't delete them if one of 2 dups is still used

const surfaceInlines = {};
const previousSurfaceInlines = {};

const add = shader => {
const existingId = findShaderId(shaders, shader);
const id = existingId || _uid ++;
let promise;
if (!existingId) {
const d = defer();
names[id] = shader.name;
shaders[id] = shader;
shadersReferenceCounters[id] = 0;
shadersCompileResponses[id] = promise = d.promise;
Shaders.emit("add", id, shader, (error, result) => {
if (error)
d.reject(error);
else
d.resolve(result);
});
}
else {
promise = shadersCompileResponses[id];
}
return { id, promise };
};

const remove = id => {
delete shaders[id];
delete names[id];
delete shadersReferenceCounters[id];
delete shadersCompileResponses[id];
Shaders.emit("remove", id);
};

const getShadersToRemove = () =>
Object.keys(shadersReferenceCounters)
.filter(id => shadersReferenceCounters[id] <= 0)
.map(id => parseInt(id, 10));

let scheduled;
const gcNow = () => {
clearTimeout(scheduled);
getShadersToRemove().forEach(remove);
};
const scheduleGC = () => {
// debounce the shader deletion to let a last chance to a future dup shader to appear
// the idea is also to postpone this operation when the app is not so busy
const noDebounce = getShadersToRemove().length > 20;
if (!noDebounce) clearTimeout(scheduled);
scheduled = setTimeout(gcNow, 500);
};

const sameShader = (a, b) => a.frag === b.frag;

const findShaderId = (shaders, shader) => {
for (let id in shaders) {
if (sameShader(shaders[id], shader)) {
return parseInt(id, 10);
}
}
return null;
};

const logError = name => error =>
console.error( //eslint-disable-line no-console
"Shader '" + name + "' failed to compile:\n" + error
);

const Shaders = {
create (obj) {

_onSurfaceWillMount (surfaceId) {
surfaceInlines[surfaceId] = [];
},

_onSurfaceWillUnmount (surfaceId) {
surfaceInlines[surfaceId].forEach(id =>
shadersReferenceCounters[id]--);
delete surfaceInlines[surfaceId];
delete previousSurfaceInlines[surfaceId];
scheduleGC();
},

_beforeSurfaceBuild (surfaceId) {
previousSurfaceInlines[surfaceId] = surfaceInlines[surfaceId];
surfaceInlines[surfaceId] = [];
},

// Resolve the shader field of GL.Node.
// it can be an id (created with Shaders.create) or an inline object.
_resolve (idOrObject, surfaceId, compileHandler) {
if (typeof idOrObject === "number") return idOrObject;
const { id, promise } = add({ name: INLINE_NAME, ...idOrObject });
if (compileHandler) {
promise.then(
result => compileHandler(null, result),
error => compileHandler(error));
}
else {
promise.catch(logError(Shaders.getName(id)));
}
const inlines = surfaceInlines[surfaceId];
inlines.push(id);
return id;
},

_afterSurfaceBuild (surfaceId) {
previousSurfaceInlines[surfaceId].forEach(id =>
shadersReferenceCounters[id]--);
surfaceInlines[surfaceId].forEach(id =>
shadersReferenceCounters[id]++);
delete previousSurfaceInlines[surfaceId];
scheduleGC();
},

// Exposed methods

create (obj, onAllCompile) {
invariant(typeof obj === "object", "config must be an object");
const result = {};
for (let key in obj) {
const compileErrors = {}, compileResults = {};
Promise.all(Object.keys(obj).map(key => {
const shader = obj[key];
invariant(typeof shader === "object" && typeof shader.frag === "string",
"invalid shader given to Shaders.create(). A valid shader is a { frag: String }");
const id = _uid ++;
if (!shader.name) shader.name = key;
names[id] = shader.name;
shaders[id] = shader;
this.emit("add", id, shader);
const {id, promise} = add({ name: key, ...shader });
result[key] = id;
}
shadersReferenceCounters[id] ++;
return promise.then(
result => compileResults[key] = result,
error => compileErrors[key] = error
);
}))
.then(() => {
if (onAllCompile) {
onAllCompile(
Object.keys(compileErrors).length ? compileErrors : null,
compileResults);
}
else {
Object.keys(compileErrors).forEach(key =>
logError(Shaders.getName(result[key]))(compileErrors[key]));
}
});
return result;
},
remove (id) {
invariant(id in shaders, "There is no such shader '%s'", id);
delete shaders[id];
delete names[id];
this.emit("remove", id);
},

get (id) {
return shaders[id];
return Object.freeze(shaders[id]);
},

getName (id) {
return names[id];
},

list () {
return Object.keys(shaders);
},

exists (id) {
return typeof id === "number" && id >= 1 && id < _uid;
return id in shaders;
},

gcNow,

...EventEmitter.prototype
};

Expand Down
40 changes: 32 additions & 8 deletions src/createSurface.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ const {
} = React;
const invariant = require("invariant");
const { fill, resolve, build } = require("./data");
const Shaders = require("./Shaders");
const findGLNodeInGLComponentChildren = require("./data/findGLNodeInGLComponentChildren");
const invariantStrictPositive = require("./data/invariantStrictPositive");

let _glSurfaceId = 1;

function logResult (data, contentsVDOM) {
if (typeof console !== "undefined" &&
console.debug // eslint-disable-line
Expand All @@ -22,6 +25,13 @@ module.exports = function (renderVcontainer, renderVcontent, renderVGL, getPixel
constructor (props, context) {
super(props, context);
this._renderId = 1;
this._id = _glSurfaceId ++;
}
componentWillMount () {
Shaders._onSurfaceWillMount(this._id);
}
componentWillUnmount () {
Shaders._onSurfaceWillUnmount(this._id);
}
getGLCanvas () {
return this.refs.canvas;
Expand All @@ -32,6 +42,7 @@ module.exports = function (renderVcontainer, renderVcontent, renderVGL, getPixel
return c.captureFrame.apply(c, arguments);
}
render() {
const id = this._id;
const renderId = this._renderId ++;
const props = this.props;
const {
Expand Down Expand Up @@ -65,14 +76,27 @@ module.exports = function (renderVcontainer, renderVcontent, renderVGL, getPixel

const { via, childGLNode } = glNode;

const { data, contentsVDOM, imagesToPreload } =
resolve(
fill(
build(
childGLNode,
context,
preload,
via)));
let resolved;
try {
Shaders._beforeSurfaceBuild(id);
resolved =
resolve(
fill(
build(
childGLNode,
context,
preload,
via,
id)));
}
catch (e) {
throw e;
}
finally {
Shaders._afterSurfaceBuild(id);
}

const { data, contentsVDOM, imagesToPreload } = resolved;

if (debug) logResult(data, contentsVDOM);

Expand Down
6 changes: 3 additions & 3 deletions src/data/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ const invariantStrictPositive = require("./invariantStrictPositive");

//// build: converts the gl-react VDOM DSL into an internal data tree.

module.exports = function build (GLNode, context, parentPreload, via) {
module.exports = function build (GLNode, context, parentPreload, via, surfaceId) {
const props = GLNode.props;
const shader = props.shader;
const shader = Shaders._resolve(props.shader, surfaceId, props.onShaderCompile);
const GLNodeUniforms = props.uniforms;
const {
width,
Expand Down Expand Up @@ -96,7 +96,7 @@ module.exports = function build (GLNode, context, parentPreload, via) {
children.push({
vdom: value,
uniform: name,
data: build(childGLNode, newContext, preload, via)
data: build(childGLNode, newContext, preload, via, surfaceId)
});
}
else {
Expand Down

0 comments on commit be7bb3c

Please sign in to comment.