/* Create the zone table */
zone_table = JS_NewArrayObject(ctx, MAX_ZONES, NULL);
if (!zone_table) {
fprintf(stderr, "Failed to create zone table!\n");
return -1;
}
if (!JS_DefineProperty(ctx, global, "zone_table",
OBJECT_TO_JSVAL(zone_table), NULL, NULL,
PROP_READONLY)) {
fprintf(stderr, "Could not add zone table to global!\n");
return -1;
}
/* Create the obj_proto table */
obj_proto_table = JS_NewArrayObject(ctx, NUM_VNUM, NULL);
if (!obj_proto_table) {
fprintf(stderr, "Failed to create obj_proto table!\n");
return -1;
}
if (!JS_DefineProperty(ctx, global, "obj_proto_table",
OBJECT_TO_JSVAL(obj_proto_table), NULL, NULL,
PROP_READONLY)) {
fprintf(stderr, "Could not add obj_proto table to global!\n");
return -1;
}
////////////////////////////////////////////////////////////////////////////
// //
// NAMESPACES //
// //
// This module allows clients to register private namespaces, detect //
// collisions, and find module access points using namespaces, rather //
// than global identifiers //
// //
////////////////////////////////////////////////////////////////////////////
function Namespaces() {
var debug = true;
var _Namespace = {};
var _topLevel;
var _callouts = {};
var _recurse_node = "";
if (!(this instanceof Namespaces)) {
return new Namespaces();
};
function NamespaceError(message) {
if (!(this instanceof NamespaceError)) {
return new NamespaceError(message);
}
this.name = "NamespaceError";
this.message = message;
return this;
}
NamespaceError.inherits(Error);
function Node(parent, name) {
if (!(this instanceof Node)) {
return new Node(parent);
}
this._affixes = {};
this._parent = parent;
this._callout = undefined;
this._interface = undefined;
this._name = name;
return this;
};
Node.prototype.append = function(affix, n) {
this._affixes[affix] = n;
};
Node.prototype.hasAffix = function(affix) {
return this._affixes.hasOwnProperty(affix);
};
Node.prototype.getAffix = function(affix) {
return this._affixes[affix];
};
Node.prototype.deleteAffix = function(affix) {
delete this._affixes[affix];
return this;
};
Node.prototype.erase = function() {
this._interface = undefined;
if (this.hasOwnProperty("_callout")) {
delete _callouts[this._callout];
}
this._callout = undefined;
};
Node.prototype.detach = function() {
this._parent = undefined;
};
Node.prototype.calloutMatches = function(callout) {
return !this.hasOwnProperty("_callout") || this._callout === undefined ||
this._callout === null || this._callout === callout;
};
Node.prototype.getInterface = function() {
return this.hasOwnProperty("_interface") ? this._interface : null;
};
Node.prototype.init = function(impl, callout) {
if (impl) {
this._interface = impl;
}
if (callout) {
this._callout = callout;
}
};
Node.prototype.getParent = function() {
return this._parent;
};
Node.prototype.getChildren = function() {
var out = [];
for (var affix in this._affixes) {
if (this._affixes.hasOwnProperty(affix)) {
out.push(affix);
}
}
return out;
};
Node.prototype.hasChildrenOrData = function() {
for (var affix in this._affixes) {
if (this._affixes.hasOwnProperty(affix)) {
return true;
}
}
if (this._interface) {
return true;
}
return false;
};
Node.prototype.recurse = function(func) {
if (this._interface) {
if (debug) {
_recurse_node = this._name;
}
func(this._interface);
}
for (var affix in this._affixes) {
if (this._affixes.hasOwnProperty(affix)) {
this._affixes[affix].recurse(func);
}
}
};
function onAllNodes(func)
{
_topLevel.recurse(func);
};
// Namespace.register
//
// Adds namespace tree if any prepended Namespace
// exist in name
//
// @param name - semicolon delimited namespace identifier
// @param callout - if present, a function name which should be searched
// for in all interfaces, called and passed our module
// @param impl - the interface module provided by this namespace
// @param descr - a description of this namespace
function register(name, callout, impl, descr) {
if (_Namespace.hasOwnProperty(name) &&
!_Namespace[name].calloutMatches(callout)) {
throw NamespaceError(
"Namespace " + name + " already defined with another callout"
);
}
this[name] = impl;
if (callout && _callouts.hasOwnProperty(callout) &&
_callouts[callout].namespace !== name) {
throw NamespaceError(
"Namespace " + name + " attempting to reuse callout defined by " +
_callouts[callout].namespace
);
}
if (impl && !(impl instanceof Object)) {
throw NamespaceError(
"Namespace " + name + " has non-object interface"
);
}
var namespace_tree = name.split(":");
if (namespace_tree.length >= 1) {
var prefix = "";
var outer = _topLevel;
for (var i = 0, len = namespace_tree.length; i < len; i++) {
var affix = namespace_tree[i];
prefix += affix;
if (!outer.hasAffix(affix)) {
var n = new Node(outer, affix);
outer.append(affix, n);
_Namespace[prefix] = n;
}
prefix += ":";
outer = outer.getAffix(affix);
}
outer.init(impl, callout);
// If we have an implementation, scan for callouts
if (impl) {
for (var func in _callouts) {
if (!_callouts.hasOwnProperty(func)) {
continue;
}
var module = _callouts[func].module;
if (impl.hasOwnProperty(func)) {
if (debug) {
mudlog("Making callout " + func + " for " + name);
}
impl[func](module);
}
}
if (impl.hasOwnProperty("initialize")) {
impl.initialize();
}
}
// If we added a callout, check if any nodes need to re-register
if (callout) {
var callout_obj = {
namespace: name,
module: impl
};
_callouts[callout] = callout_obj;
// Call any pre-existing nodes that need to register
// with the new module
onAllNodes(
function(node) {
if (node.hasOwnProperty(callout)) {
if (debug) {
mudlog("Making callout " + callout + " for " +
_recurse_node);
}
node[callout](impl);
}
});
}
return outer;
}
return null;
};
// Namespace.delete
//
// Removes a namespace from namespace tree
//
// @param name - namespace to remove
// @param recursive - delete all nodes beneath this node
// @param cleanup - clean up unused parent nodes
function deleteInternal(name, recursive, cleanup) {
cleanup = (cleanup === undefined) ? true : false;
if (!_Namespace.hasOwnProperty(name)) {
throw NamespaceError(
"Namespace " + name + " undefined or already defined with another key"
);
}
var victim = _Namespace[name];
victim.erase();
if (recursive) {
var children = victim.getChildren();
for (var i = 0, len = children.length; i < len; i++) {
var sub_space = name + ":" + children[i];
try {
deleteInternal(sub_space, true, false);
} catch (e) {
error_log("Unable to delete protected subspace " + sub_space);
}
}
}
var namespace_tree = name.split(":");
var prefix = name;
for (len = namespace_tree.length, i = len - 1; i >= 0; i–) {
if (victim.hasChildrenOrData()) {
return;
}
var affix = namespace_tree[i];
var outer = victim.getParent();
assert_true("Tree Integrity test", outer.hasAffix(affix));
outer.deleteAffix(affix);
assert_false("Tree Integrity test", outer.hasAffix(affix));
victim.detach();
victim = outer;
delete _Namespace[prefix];
prefix = prefix.delimitedParent(':');
if (!cleanup) {
return;
}
}
};
// Namespace.getInterface
//
// Gets a module interface from a namespace name
//
// @param name - semicolon delimited namespace identifier
function getInterface(name) {
if (_Namespace.hasOwnProperty(name)) {
return _Namespace[name].getInterface();
}
return null;
};
// Namespace.getChildren
//
// Gets a list of child Namespace given a namespace identifier
//
// @param name - semicolon delimited namespacey identifier
function getChildren(name) {
if (_Namespace.hasOwnProperty(name)) {
return _Namespace[name].getChildren();
}
return null;
};
function resetInternal() {
_Namespace = {};
_topLevel = new Node(null, "");
_Namespace[""] = _topLevel;
};
resetInternal();
return {
register: register,
delete: deleteInternal,
getInterface: getInterface,
getChildren: getChildren,
reset: function() {
resetInternal();
}
};
};
(function unitTest() {
try {
error_log("BEGIN UNIT TEST: Namespace");
var Namespace = new Namespaces();
// Set to true to debug this test
var log = true;
var impl = { a: "test" };
var ns = Namespace.register("Animal:Mammal:Dog:Snoopy", "Secret", impl);
assert_true ("Namespace Test 2A: register", ns.getInterface() === impl, log);
assert_true ("Namespace Test 2B: register",
Namespace.getInterface("Animal:Mammal:Dog:Snoopy") === impl, log);
assert_equals ("Namespace Test 3: getChildren",
Namespace.getChildren("Animal:Mammal:Dog"), [ "Snoopy" ], log);
assert_equals("Namespace Test 4: get top level",
Namespace.getChildren("").length, 1, log);
assert_true ("Namespace Test 5: get top level",
Namespace.getChildren("").indexOf("Animal") >= 0, log);
test_exception_func("Namespace Test 6: exceptions",
Namespace.register,
["Animal:Mammal:Dog:Snoopy", "Oops", { a: "bad" }],
"NamespaceError", log);
var impl2 = { a: "test2" };
var ns2 = Namespace.register("Animal:Mammal:Dog:Snoopy", "Secret", impl2);
assert_true ("Namespace Test 7: re-register", ns2.getInterface() === impl2, log);
var impl3 = { a: "test3" };
Namespace.register("Animal:Mammal", "FieFouFum", impl3);
assert_true ("Namespace Test 8: sub-register",
Namespace.getInterface("Animal:Mammal") === impl3, log);
test_exception_func("Namespace Test 9: exception on bad deregister",
Namespace.delete,
["Animal:Mammal:Doog"],
"NamespaceError", log);
Namespace.register("Animal:Mammal:Dog:Snoopy:Bone:Pillow", null, impl2);
Namespace.register("Animal:Mammal:Dog:Snoopy:Bone", "VerySecret", impl);
Namespace.delete("Animal:Mammal:Dog:Snoopy", "Secret", true);
error_log("PASSED UNIT TEST: Namespace");
} catch (e) {
var errstr = e.name + ": " + e.message;
error_log(errstr);
error_log("FAILED UNIT TEST: Namespace");
throw e;
}
}());
if (typeof Global === "undefined") {
var Namespace = new Namespaces();
var Global = Namespace;
Namespace.register("Namespace", "withNamespace", Namespace, "Global namespace");
}
/////////////////////////////////////////////////////////////////
// Reports
/////////////////////////////////////////////////////////////////
Namespace.register("Reports", "withReports", (function() {
const THIS_MODULE = "Reports";
const MAX_REPORT = 3000;
// Imports
var FileManager;
function createReport(type, player, data) {
var lower = type.toLowerCase();
var id = FileManager.createUniqueId("Reports", lower+".id");
var abuse = (type === "Abuse");
var fname = "Reports/" + type.capitalize() +
(abuse ? "s" : "") + "/" + id;
var schema = "Messages:" + lower;
var report = { text: data,
playerId: player.playerId };
if (!abuse) {
report.player = player.name;
}
FileManager.save(fname, report, schema, player.name);
return id;
};
function withFileManager(module) {
FileManager = module;
};
function withSchemaManager(SchemaManager) {
SchemaManager.importSchema("Messages:bug");
SchemaManager.importSchema("Messages:typo");
SchemaManager.importSchema("Messages:idea");
SchemaManager.importSchema("Messages:petition");
SchemaManager.importSchema("Messages:abuse");
};
return {
MAX_REPORT : MAX_REPORT,
withFileManager : withFileManager,
withSchemaManager : withSchemaManager,
createReport : createReport
};
})());
…
loadF: function(f) { try { eval(fs.readFileSync(o.path+f)+'') } catch(err) { srv.log(err) } }
…
…
loadF: function(f) { try { eval(fs.readFileSync(o.path+f)+'') } catch(err) { srv.log(err) } }
…
Example:
Now say you need to reload the module defining player…
Now what happens?
Obviously it is a really dumb idea to code like this. The example is just for illustration. The reason this happens is because we have redefined the Player function and now it has a new prototype. Hence, changes to the prototype only affect new players. Older, pre-existing players don't get your fancy new code updates.
Exactly the opposite of what you want for reloadable code. I'm curious how others have gone about solving that problem.. I'll post my solution, which turned out to be a bit non-trivial.
Phew!!! A lot of the fancy footwork there is to stop you from accidentally invoking the constructor without new.
Now you can do things like this..
And now I have a Player class that inherits from the Mobile class. I can change the prototypes on both classes, and all player class objects inherit those changes.
You might spy that I have to reference globals in the above functions. I have a module system that encapsulates functionality without polluting global state, but I can't use it here. This is the only thing I don't entirely like, but it turns out to be necessary, since the constructor functions can only by defined once (redefining them would create a new prototype, creating all the problems above). If I used local references to modules, then I wouldn't get new values when those modules are reloaded.
It might look like this poses a problem since I can't redefine the constructors, but this is really simple to work-around: