Skip to content

Instantly share code, notes, and snippets.

@egonelbre
Last active December 10, 2015 01:29
Show Gist options
  • Save egonelbre/4358887 to your computer and use it in GitHub Desktop.
Save egonelbre/4358887 to your computer and use it in GitHub Desktop.
// allows easily making an Actor with a Role
// if it's already an actor the context will be attached.
Object.defineProperty(Object.prototype, "as", {enumerable: false, value : function(role){
// this will add a role to the object
if(this.__isActor__) {
this.__addRole__(role);
return this;
}
// if this is not an actor -
// an object that can play a role, we must make a new actor
var actor = new Actor(this);
actor.__addRole__(role);
return actor;
}});
function sameObject(a, b){
if(a.__isActor__)
a = a.original;
if(b.__isActor__)
b = b.original;
return a === b;
}
// this defines a Context
function Context(fn) {
return function(){
fn.__ctx__ = GUID();
return fn.apply(null, arguments);
};
}
// this is for creating a new Role
// also attaches the context of the methods
// should only be called in a Context
function Role(methods) {
var role = {
__ctx__: currentContext(),
__methods__: methods
};
for(var e in methods) {
methods[e].__ctx__ = role.__ctx__;
}
return role;
}
// for using anonymous methods
// this will attach the correct context otherwise the function will be contextless
function Fn(fn) {
var ctx = currentContext();
fn.__ctx__ = ctx;
return fn;
}
function Methods(obj){
var ctx = currentContext();
obj.__ctx__ = ctx;
for(var name in obj){
if(typeof obj[name] ){
obj[name].__ctx__ = ctx;
}
}
return obj;
}
// this defines an actor capable of dispatching to roles depending on context
// ! should not be used directly
function Actor(original) {
// actor specific things
this.__isActor__ = true;
this.__original__ = original;
var contexts = { /*context : [Role1, Role2, Role3]*/ };
// this method makes a dispatcher that finds the correct method
// depending on the calling context
var mkDispatcher = function(name) {
var fx = function() {
var ctx = currentContext(),
roles = contexts[ctx],
fn = original[name];
if(roles) {
for(var i = 0; i < roles.length; i += 1) {
var methods = roles[i].__methods__;
if(methods[name]) {
fn = methods[name];
break;
}
}
}
if(!fn){
throw new Error("undefined method '" + name + "'");
}
return fn.apply(this, arguments);
};
fx.__ctx__ = "Dispatcher[" + name + "]"; // for debugging
return fx;
};
// for dispatching get/set to the original
var redirect = function(from, to, name){
from.__defineGetter__(name, function(){
return to[name];
});
from.__defineSetter__(name, function(v){
to[name] = v;
});
};
// for dispatching calls to the original
this.constructor = original.constructor;
for(var name in original){
if(typeof original[name] === "function"){
this[name] = mkDispatcher(name);
} else {
redirect(this, original, name);
}
}
// this just adds a role to the actor table
// and makes additional dispatchers for role methods
this.__addRole__ = function(role) {
var ctx = role.__ctx__;
if(!contexts[ctx])
contexts[ctx] = [];
contexts[ctx].unshift(role);
var methods = role.__methods__;
for(var name in methods) {
if(!methods.hasOwnProperty(name) || this.hasOwnProperty(name))
continue;
this[name] = mkDispatcher(name);
}
};
}
// this finds the current context
// undefined if, none found
// ! should not be used directly
function currentContext() {
return currentContext.caller.caller.__ctx__;
}
// used for uniquely identifyig contexts
function GUID() {
// http://www.ietf.org/rfc/rfc4122.txt
var s = [];
var hexDigits = "0123456789abcdef";
for(var i = 0; i < 36; i++) {
s[i] = hexDigits[Math.random() * 0x10 | 0];
}
s[14] = "4";
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1);
s[8] = s[13] = s[18] = s[23] = "-";
var uuid = s.join("");
return uuid;
}
<!doctype html>
<title>DCI Test</title>
<pre id="log"></pre>
<script src="dci.js"></script>
<script>
write = function() {
var lg = document.getElementById("log"),
args = Array.prototype.slice.apply(arguments),
line = args.join(", ") + "<br>";
lg.innerHTML += line;
};
function makePlayerTalk(player) {
player.say();
}
var Battle = Context(function(alpha, beta) {
var Bear = Role({
say: function() {
write( "[" + this.name + "] " + "Grrrr.....");
},
touch: function(other) {
this.say();
other.say();
}
});
var Lion = Role({
say: function() {
write( "[" + this.name + "] " + "Meow....");
},
touch: function(other) {
this.say();
other.say();
}
});
return Methods({
doit : function(){
var lion = alpha.as(Lion),
alphaBear = alpha.as(Bear),
bear = beta.as(Bear);
write("# Conversing");
lion.say();
bear.say();
alphaBear.say();
write("# lion touching the bear");
lion.touch(bear);
write("# making alpha talk");
makePlayerTalk(lion);
write("# making beta talk");
makePlayerTalk(bear);
}
});
});
var player = {
name: "Jack",
say: function() {
write( "[" + this.name + "] " + "says Hello!");
}
};
var cpu = {
name: "Cyborg",
say: function() {
write( "[" + this.name + "] " + "bleeps Hello!");
}
};
var battle = Battle(player, cpu);
battle.doit();
/* output
# Conversing
[Jack] Meow....
[Cyborg] Grrrr.....
[Jack] Grrrr.....
# lion touching the bear
[Jack] Meow....
[Cyborg] Grrrr.....
# making alpha talk
[Jack] says Hello!
# making beta talk
[Cyborg] bleeps Hello!
*/
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment