dreemgl
Version:
DreemGL is an open-source multi-screen prototyping framework for mediated environments, with a visual editor and shader styling for webGL and DALi runtimes written in JavaScript. As a toolkit for gpu-accelerated multiscreen development, DreemGL includes
601 lines (541 loc) • 16.4 kB
JavaScript
/* DreemGL is a collaboration between Teeming Society & Samsung Electronics, sponsored by Samsung and others.
Copyright 2015-2016 Teeming Society. Licensed under the Apache License, Version 2.0 (the "License"); You may not use this file except in compliance with the License.
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.*/
define.class(function(require, $server$, dataset){
// internal, Sourceset is a dataset-api on source
var jsparser = require('$system/parse/onejsparser')
var jsformatter = require('$system/parse/jsformatter')
var astscanner = require('$system/parse/astscanner')
var WiredWalker = require('$system/parse/wiredwalker')
this.attributes = {
change: Config({type:Event})
}
function findRenderFunction(ast){
var steps = ast.steps[0].body.steps
for(var i = 0; i < steps.length; i++){
var step = steps[i]
if(step.type === 'Assign' && step.left.type === 'Key' &&
step.left.key.name === 'render'){
return step.right
}
}
}
function findReturnArray(body){
var steps = body.steps
for(var i = 0; i < steps.length; i++){
var step = steps[i]
if(step.type === 'Return'){
return step.arg
}
}
}
this.atConstructor = function(source){
if(source) this.parse(source)
this.last_source = source
}
this.fork = function(callback){
this.undo_stack.push(this.last_source)
this.redo_stack.length = 0
callback(this)
// lets reserialize
this.last_source = this.stringify()
this.process()
this.notifyAssignedAttributes()
// save to disk.
this.emit('change')
}
this.addBlock = function(folder, classname){
var id = 0
var uname = classname + id
while(uname in this.data.childnames){
id++
uname = classname + id
}
if (folder) {
// add it to the deplist.
var deps = this.ast.steps[0].params
var $folder = '$'+folder.replace(/\//g,'$')+'$'
var dir = '$$'
for(var i = 0; i < deps.length; i ++){
var name = deps[i].id.name
if(name === $folder) break
}
if(i === deps.length){
deps.push(
{type:'Def',id:{type:'Id', name:$folder}},
{type:'Def',id:{type:'Id', name:classname}}
)
}
else{
for(var j = i; j < deps.length; j ++){
if(deps[j].id.name === classname) break
}
if(j === deps.length){
deps.splice(i,0,
{type:'Def',id:{type:'Id', name:classname}}
)
}
}
}
this.data.retarray.elems.push({
type:"Call",
fn:{type:"Id",name:classname},
args:[{
type:"Object",
keys:[
{
key:{type:"Id",name:"name"},
value:{type:"Value",kind:"string",value:uname}
},
{key:{type:"Id",name:"flowdata"}, value:genFlowDataObject({x:20,y:420})}
]
}]
})
}
this.removeBlock = function(blockname){
// lets remove this thing
node = this.data.childnames[blockname]
// lets find it
var id = this.data.retarray.elems.indexOf(node.node)
this.data.retarray.elems.splice(id, 1)
}
function genFlowDataObject(data){
var obj = {
type:"Object",
keys:[]
}
for(var key in data){
obj.keys.push({
kind:"init",
key:{type:"Id",name:key},
value:{type:"Value",kind:"num",value:data[key]}
})
}
return obj
}
this.setFlowData = function(block, data){
var target = this.data.childnames[block]
var fdn = target.flowdatanode
if (!fdn) {
return;
}
fdn.value = genFlowDataObject(data)
}
this.extractRPCCalls = function(str) {
var found = []
var mast = jsparser.parse(str)
if (mast) {
var wiredwalker = new WiredWalker()
var state = wiredwalker.newState()
wiredwalker.expand(mast, null, state)
var refs = state.references;
for (var k = 0;k < refs.length;k++) {
var con = refs[k].join('.')
if (found.indexOf(con) < 0) {
found.push(con)
}
}
}
if (!found.length) {
var r = /(this\.rpc\.[a-zA-Z0-9]+\.[a-zA-Z0-9]+)/g
var m;
while (m = r.exec(str)) {
found.push(m[1])
}
}
return found;
}
this.generateRPCKV = function(key, block, port) {
return {
kind:"init",
key:{type:"Value", kind:"string", value:key, multi:false, raw:JSON.stringify(key)},
value:{type:"Key",key:{type:"Property", name:port},
object:{type:"Key",key:{type:"Property", name:block},
object:{type:"Key",key:{type:"Property", name:"rpc"},
object:{type:"This"}
}
}
}
}
}
this.generateWire = function(key, value) {
return {
key:{name:key, type:'Id'},
value:{
type:"Call",
fn:{
type:"Id",
name:"wire"
},
args:[{
type:"Value",
kind:"string",
value:value
}]
}
}
}
this.deleteWire = function(sblock, soutput, tblock, tinput){
if (!tblock) {
return
}
var target = this.data.childnames[tblock]
if(!target) return console.error("cannot find target " + tblock)
// ok we need to do keys
var props = target.propobj.keys
for(var i = 0; i < props.length; i++){
var ast = props[i]
if (ast.key.name == tinput) {
if (sblock && soutput) {
var scanner = new astscanner(ast.value, [{type:"Call", fn:{type:"Id", name:"wire"}}, {type:"Value", kind:"string"}])
var at = scanner.at;
if (at && at.type && at.type === "Value") {
var objname = sblock + "." + soutput;
var rpcstr = "this.rpc." + objname;
var connections = this.extractRPCCalls(at.value)
var index = connections.indexOf(rpcstr)
if (index >= 0) {
connections.splice(index, 1)
}
var isarray = false;
var isobject = false;
if (connections.length === 1) {
var con = connections[0].split('.');
var blockname = con[con.length - 2];
var outputname = con[con.length - 1];
var block = this.data.childnames[blockname]
if (block) {
var outputs = block.outputs;
for (var b=0;b<outputs.length;b++) {
var output = outputs[b];
if (output.name === outputname) {
isarray = output.type === Array
isobject = output.type === Object
}
}
}
}
if (connections.length === 1 && ((at.value[0] === '[' && isarray) || (at.value[0] === '{' && isobject))) {
at.value = connections[0]
at.raw = JSON.stringify(at.value)
} else if (connections.length) {
if (at.value[0] === '{') {
var mast = jsparser.parse(at.value)
var obj = new astscanner(mast, [{type:"Object"}])
obj.scan([{type:"Value", value:objname}])
if (typeof(obj.atindex) !== "undefined") {
obj.atparent.keys.splice(obj.atindex, 1)
}
at.value = obj.toSource(obj.atparent)
at.raw = JSON.stringify(at.value)
} else {
var endcap = ']'
var lix = at.value.lastIndexOf(']')
if (lix > -1 && lix !== at.value.length - 1) {
endcap = at.value.substring(lix)
}
at.value = "[" + connections.join(",") + endcap
at.raw = JSON.stringify(at.value)
}
} else {
props.splice(i,1)
}
}
} else {
props.splice(i,1)
}
break
}
}
}
this.insertWire = function(sblock, soutput, stype, tblock, tinput) {
var target = this.data.childnames[tblock]
if (target) {
var props = target.propobj.keys
for (var i = 0; i < props.length; i++) {
if(props[i].key.name == tinput){
var ast = props[i];
var scanner = new astscanner(ast.value, [{type:"Call", fn:{type:"Id", name:"wire"}}, {type:"Value", kind:"string"}])
var at = scanner.at;
if (at && at.type && at.type === "Value") {
var value = at.value;
var rpcstr = "this.rpc." + sblock + "." + soutput;
if (value) {
var connections = this.extractRPCCalls(value)
if (connections.indexOf(rpcstr) < 0) {
connections.push(rpcstr)
}
var endcap = ']'
var lix = at.value.lastIndexOf(']')
if (lix > -1 && lix !== at.value.length - 1) {
endcap = at.value.substring(lix)
}
at.value = "[" + connections.join(",") + endcap
at.raw = JSON.stringify(at.value)
return true;
}
}
console.log("is this really possible?", ast)
}
}
if (stype === "Array") {
props.push(this.generateWire(tinput, "this.rpc." + sblock + '.' + soutput))
} else {
props.push(this.generateWire(tinput, "[this.rpc." + sblock + '.' + soutput + "]"))
}
return true;
}
return false;
}
this.mergeWire = function(sblock, soutput, stype, tblock, tinput) {
var target = this.data.childnames[tblock]
if (target) {
var props = target.propobj.keys
for (var i = 0; i < props.length; i++) {
if(props[i].key.name == tinput){
var ast = props[i];
var scanner = new astscanner(ast.value, [{type:"Call", fn:{type:"Id", name:"wire"}}, {type:"Value", kind:"string"}])
var at = scanner.at;
if (at && at.type && at.type === "Value") {
var value = at.value;
var objname = sblock + "." + soutput;
if (value) {
var mast = jsparser.parse(value)
var obj = new astscanner(mast, [{type:"Object"}])
obj.scan([{type:"Value", value:objname}])
if (obj.at && obj.at.type === "Value") {
obj.atparent.keys.splice(obj.atindex, 1, this.generateRPCKV(objname, sblock, soutput))
} else if (obj.at && obj.at.type === "Object") {
if (!obj.at.keys) {
obj.at.keys = []
}
var newkey = this.generateRPCKV(objname, sblock, soutput)
obj.at.keys.push(newkey)
at.value = obj.toSource()
at.raw = JSON.stringify(at.value)
} else {
var connections = this.extractRPCCalls(value)
var newobj = {type:"Object", keys:[]}
newobj.keys.push(this.generateRPCKV(objname, sblock, soutput))
for (var z=0;z<connections.length;z++) {
var con = connections[z]
var parts = con.split(".")
var block = parts[parts.length - 2]
var port = parts[parts.length - 1]
var conname = block + "." + port
newobj.keys.push(this.generateRPCKV(conname, block, port))
}
at.value = obj.toSource(newobj)
at.raw = JSON.stringify(at.value)
}
return true;
}
}
}
}
if (stype === "Object") {
props.push(this.generateWire(tinput, "this.rpc." + sblock + '.' + soutput))
} else {
props.push(this.generateWire(tinput, "{'" + sblock + "." + soutput + "':this.rpc." + sblock + '.' + soutput + "}"))
}
return true;
}
return false;
}
this.createWire = function(sblock, soutput, tblock, tinput){
var target = this.data.childnames[tblock]
if(!target) return console.error("cannot find target " + tblock)
// ok we need to do keys
var props = target.propobj.keys
for(var i = 0; i < props.length; i++){
if(props[i].key.name == tinput) break
}
var wire = this.generateWire(tinput, 'this.rpc.' + sblock + '.' + soutput)
if(i === props.length){
props.push(wire)
} else{
props[i].value.value = wire.value
}
}
this.process = function(){
var resolver = {}
var deps = this.ast.steps[0].params
var args = this.classconstr.module.factory.body.class_args
for(var i = 0; i < deps.length; i++){
resolver[ deps[i].id.name ] = args[i]
}
// we need to find the render function in the composition root
// so how shall we do that.
// well..
// lets write the code
// lets find the return array
var ret = findReturnArray(findRenderFunction(this.ast).body)
var data = this.data = {
retarray: ret,
name:'root',
node:ret.elems,
children:[],
childnames:{}
}
// now we need to walk this fucker.
function walkArgs(array, output){
for(var i = 0; i < array.length; i++){
var item = array[i]
if(item.type === 'Object'){
output.propobj = item
// lets put some props on there
// whats the name of this thing?
var keys = item.keys
for(var j = 0; j < keys.length; j++){
var key = keys[j]
var name = key.key.name
var value = key.value
if(name === 'flowdata'){
var fdoutput = output.flowdata = {}
output.flowdatanode = key
var flowdata = value
for(var k = 0; k < flowdata.keys.length; k++){
var fditem = flowdata.keys[k]
var value
if(fditem.value.type === 'Unary'){
value = fditem.value.op === '-'? -fditem.value.arg.value: fditem.value.arg.value
}
else{
value = fditem.value.value
}
fdoutput[fditem.key.name] = value
}
}
else if(name === 'name'){
output.name = key.value.value
data.childnames[output.name] = output
}
else if(key.value.type === 'Call' && key.value.fn.name === 'wire'){
var wire = key.value
var str = wire.args[0].value
var containsrpc = str.indexOf('this.rpc')
if (containsrpc > -1) {
if(!output.wires) output.wires = []
var parts = str.slice(9).split('.')
if (parts.length === 2) {
output.wires.push({
from:parts[0],
output:parts[1],
input:name
})
} else {
var r = /this\.rpc\.([a-zA-Z0-9]+)\.([a-zA-Z0-9]+)/g
var m;
while (m = r.exec(str)) {
if(!output.wires) output.wires = []
output.wires.push({
from:m[1],
output:m[2],
input:name,
multi:true
})
}
}
}
}
}
continue
}
}
}
function walkComposition(array, output){
for(var i = 0; i < array.length; i++){
var item = array[i]
if(item.type !== 'Call') continue
var classname
if(item.fn.type === 'Key' && item.fn.object.type === 'This'){
classname = 'this.' + item.fn.key.name
}
else if(item.fn.type === 'Id'){
classname = item.fn.name
}
else {
console.error(classname = "Please implement in sourceset.js")
}
var child = {
classname: classname,
node: item,
children:[],
inputs:[],
outputs:[],
editables:[]
}
// we haz classname.
var clazz = resolver[classname]
var attribs = clazz.prototype._attributes
for(var key in attribs){
if (attribs.hasOwnProperty(key)) {
var attrib = attribs[key]
if(attrib.flow){
var con = {
name: key,
title: key,
type: attrib.type,
attrib: attrib
};
if (attrib.flow === 'in'){
child.inputs.push(con)
} else if(attrib.flow === 'out'){
child.outputs.push(con)
} else if(attrib.flow === 'inout'){
child.inputs.push(con);
child.outputs.push({
name:con.name,
title:con.title,
type:con.type,
attrib:con.attrib
})
} else if (attrib.flow === 'edit') {
child.editables.push(con);
}
}
}
}
output.children.push(child)
walkArgs(item.args, child)
//walkTree(item.args, child)
}
}
walkComposition(ret.elems, this.data)
}
// convert a string in to a meaningful javascript object for this dataset. The default is JSON, but you could use this function to accept any format of choice.
this.parse = function(classconstr){
var source = classconstr.module.factory.body.toString()
this.classconstr = classconstr
// lets create an AST
this.ast = jsparser.parse(source)
this.process()
this.notifyAssignedAttributes()
}
// convert an object in to a string. Defaults to standard JSON, but you could overload this function to provide a more efficient fileformat. Do not forget to convert the JSONParse function as well.
this.stringify = function(){
var buf = {
out:'',
charCodeAt: function(i){return this.out.charCodeAt(i)},
char_count:0
};
jsformatter.walk(this.ast, buf, {}, function(str){
if(str === '\n'){
this.last_is_newline = true;
return
}
if(str === '\t' && this.last_is_newline){
str = '\n';
for (var i = 0; i < this.actual_indent;i++) {
str += ' '
}
}
this.last_is_newline = false;
buf.char_count += str.length;
buf.out += str
});
return buf.out
}
})