broke the dammn thing

This commit is contained in:
DadaMonad 2015-02-19 15:55:05 +00:00
parent 792440a71d
commit 860934de06
17 changed files with 1098 additions and 1016 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -164,6 +164,17 @@ module.exports = function() {
return success;
};
Operation.prototype.getCustomType = function() {
if (this.custom_type == null) {
throw new Error("This operation was not initialized with a custom type");
}
if (this.custom_type.constructor === String) {
this.custom_type = new this.custom_types[this.custom_type]();
this.custom_type._setModel(this);
}
return this.custom_type;
};
return Operation;
})();

View File

@ -11,8 +11,11 @@ module.exports = function() {
ops.MapManager = (function(_super) {
__extends(MapManager, _super);
function MapManager(uid) {
this.map = {};
function MapManager(custom_type, uid) {
if (custom_type != null) {
this.custom_type = custom_type;
}
this._map = {};
MapManager.__super__.constructor.call(this, uid);
}
@ -20,7 +23,7 @@ module.exports = function() {
MapManager.prototype.applyDelete = function() {
var name, p, _ref;
_ref = this.map;
_ref = this._map;
for (name in _ref) {
p = _ref[name];
p.applyDelete();
@ -32,25 +35,50 @@ module.exports = function() {
return MapManager.__super__.cleanup.call(this);
};
MapManager.prototype.map = function(f) {
var n, v, _ref;
_ref = this._map;
for (n in _ref) {
v = _ref[n];
f(n, v);
}
return void 0;
};
MapManager.prototype.val = function(name, content) {
var o, prop, result, _ref;
var o, prop, rep, res, result, _ref;
if (arguments.length > 1) {
this.retrieveSub(name).replace(content);
if ((content != null) && (content._model != null) && content._model instanceof ops.Operation) {
rep = content._model;
} else {
rep = content;
}
this.retrieveSub(name).replace(rep);
return this;
} else if (name != null) {
prop = this.map[name];
prop = this._map[name];
if ((prop != null) && !prop.isContentDeleted()) {
return prop.val();
res = prop.val();
if (res instanceof ops.Operation) {
return res.getCustomType();
} else {
return res;
}
} else {
return void 0;
}
} else {
result = {};
_ref = this.map;
_ref = this._map;
for (name in _ref) {
o = _ref[name];
if (!o.isContentDeleted()) {
result[name] = o.val();
res = prop.val();
if (res instanceof ops.Operation) {
result[name] = res.getCustomType();
} else {
result[name] = res;
}
}
}
return result;
@ -59,7 +87,7 @@ module.exports = function() {
MapManager.prototype["delete"] = function(name) {
var _ref;
if ((_ref = this.map[name]) != null) {
if ((_ref = this._map[name]) != null) {
_ref.deleteContent();
}
return this;
@ -67,7 +95,7 @@ module.exports = function() {
MapManager.prototype.retrieveSub = function(property_name) {
var event_properties, event_this, rm, rm_uid;
if (this.map[property_name] == null) {
if (this._map[property_name] == null) {
event_properties = {
name: property_name
};
@ -78,16 +106,35 @@ module.exports = function() {
alt: this
};
rm = new ops.ReplaceManager(event_properties, event_this, rm_uid);
this.map[property_name] = rm;
this._map[property_name] = rm;
rm.setParent(this, property_name);
rm.execute();
}
return this.map[property_name];
return this._map[property_name];
};
MapManager.prototype._encode = function() {
var json;
json = {
'type': this.type,
'uid': this.getUid()
};
if (this.custom_type.constructor === String) {
json.custom_type = this.custom_type;
} else {
json.custom_type = this.custom_type._name;
}
return json;
};
return MapManager;
})(ops.Operation);
ops.MapManager.parse = function(json) {
var custom_type, uid;
uid = json['uid'], custom_type = json['custom_type'];
return new this(custom_type, uid);
};
ops.ListManager = (function(_super) {
__extends(ListManager, _super);
@ -413,7 +460,11 @@ module.exports = function() {
Replaceable.prototype.type = "Replaceable";
Replaceable.prototype.val = function() {
return this.content;
if ((this.content != null) && (this.content.getCustomType != null)) {
return this.content.getCustomType();
} else {
return this.content;
}
};
Replaceable.prototype.applyDelete = function() {

View File

@ -0,0 +1,80 @@
var YObject;
YObject = (function() {
function YObject(_at__object) {
var name, val, _ref;
this._object = _at__object != null ? _at__object : {};
if (this._object.constructor === Object) {
_ref = this._object;
for (name in _ref) {
val = _ref[name];
if (val.constructor === Object) {
this._object[name] = new YObject(val);
}
}
} else {
throw new Error("Y.Object accepts Json Objects only");
}
}
YObject.prototype._name = "Object";
YObject.prototype._getModel = function(types, ops) {
var n, o, _ref;
if (this._model == null) {
this._model = new ops.MapManager(this).execute();
_ref = this._object;
for (n in _ref) {
o = _ref[n];
this._model.val(n, o);
}
}
delete this._object;
return this._model;
};
YObject.prototype._setModel = function(_at__model) {
this._model = _at__model;
return delete this._object;
};
YObject.prototype.observe = function(f) {
return this._model.observe(f);
};
YObject.prototype.val = function(name, content) {
var n, res, v, _ref;
if (this._model != null) {
return this._model.val.apply(this._model, arguments);
} else {
if (content != null) {
return this._object[name] = content;
} else if (name != null) {
return this._object[name];
} else {
res = {};
_ref = this._object;
for (n in _ref) {
v = _ref[n];
res[n] = v;
}
return res;
}
}
};
return YObject;
})();
if (typeof window !== "undefined" && window !== null) {
if (window.Y != null) {
window.Y.Object = YObject;
} else {
throw new Error("You must first import Y!");
}
}
if (typeof module !== "undefined" && module !== null) {
module.exports = YObject;
}

View File

@ -1,6 +1,6 @@
var Engine, HistoryBuffer, adaptConnector, createY, json_ops_uninitialized;
var Engine, HistoryBuffer, adaptConnector, createY, text_ops_uninitialized;
json_ops_uninitialized = require("./Operations/Json");
text_ops_uninitialized = require("./Operations/Text");
HistoryBuffer = require("./HistoryBuffer");
@ -9,7 +9,7 @@ Engine = require("./Engine");
adaptConnector = require("./ConnectorAdapter");
createY = function(connector) {
var HB, engine, ops, ops_manager, user_id;
var HB, ct, engine, model, ops, ops_manager, user_id;
user_id = null;
if (connector.user_id != null) {
user_id = connector.user_id;
@ -21,7 +21,7 @@ createY = function(connector) {
};
}
HB = new HistoryBuffer(user_id);
ops_manager = json_ops_uninitialized(HB, this.constructor);
ops_manager = text_ops_uninitialized(HB, this.constructor);
ops = ops_manager.operations;
engine = new Engine(HB, ops);
adaptConnector(connector, engine, HB, ops_manager.execution_listener);
@ -30,11 +30,16 @@ createY = function(connector) {
ops.Operation.prototype.engine = engine;
ops.Operation.prototype.connector = connector;
ops.Operation.prototype.custom_ops = this.constructor;
return new ops.Object(HB.getReservedUniqueIdentifier()).execute();
ct = new createY.Object();
model = new ops.MapManager(ct, HB.getReservedUniqueIdentifier()).execute();
ct._setModel(model);
return ct;
};
module.exports = createY;
if ((typeof window !== "undefined" && window !== null) && (window.Y == null)) {
if (typeof window !== "undefined" && window !== null) {
window.Y = createY;
}
createY.Object = require("./Types/Object");

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -201,6 +201,16 @@ module.exports = ()->
@unchecked = uninstantiated
success
getCustomType: ()->
if not @custom_type?
throw new Error "This operation was not initialized with a custom type"
if @custom_type.constructor is String
# has not been initialized yet (only the name is specified)
@custom_type = new @custom_types[@custom_type]()
@custom_type._setModel @
@custom_type
#
# @nodoc
# A simple Delete-type operation that deletes an operation.

View File

@ -1,147 +0,0 @@
text_ops_uninitialized = require "./Text"
module.exports = ()->
text_ops = text_ops_uninitialized()
ops = text_ops.operations
#
# Manages Object-like values.
#
class ops.Object extends ops.MapManager
#
# Identifies this class.
# Use it to check whether this is a json-type or something else.
#
# @example
# var x = y.val('unknown')
# if (x.type === "Object") {
# console.log JSON.stringify(x.toJson())
# }
#
type: "Object"
applyDelete: ()->
super()
cleanup: ()->
super()
#
# Transform this to a Json. If your browser supports Object.observe it will be transformed automatically when a change arrives.
# Otherwise you will loose all the sharing-abilities (the new object will be a deep clone)!
# @return {Json}
#
# TODO: at the moment you don't consider changing of properties.
# E.g.: let x = {a:[]}. Then x.a.push 1 wouldn't change anything
#
toJson: (transform_to_value = false)->
if not @bound_json? or not Object.observe? or true # TODO: currently, you are not watching mutable strings for changes, and, therefore, the @bound_json is not updated. TODO TODO wuawuawua easy
val = @val()
json = {}
for name, o of val
if o instanceof ops.Object
json[name] = o.toJson(transform_to_value)
else if o instanceof ops.ListManager
json[name] = o.toJson(transform_to_value)
else if transform_to_value and o instanceof ops.Operation
json[name] = o.val()
else
json[name] = o
@bound_json = json
if Object.observe?
that = @
Object.observe @bound_json, (events)->
for event in events
if not event.changedBy? and (event.type is "add" or event.type = "update")
# this event is not created by Y.
that.val(event.name, event.object[event.name])
@observe (events)->
for event in events
if event.created_ isnt @HB.getUserId()
notifier = Object.getNotifier(that.bound_json)
oldVal = that.bound_json[event.name]
if oldVal?
notifier.performChange 'update', ()->
that.bound_json[event.name] = that.val(event.name)
, that.bound_json
notifier.notify
object: that.bound_json
type: 'update'
name: event.name
oldValue: oldVal
changedBy: event.changedBy
else
notifier.performChange 'add', ()->
that.bound_json[event.name] = that.val(event.name)
, that.bound_json
notifier.notify
object: that.bound_json
type: 'add'
name: event.name
oldValue: oldVal
changedBy:event.changedBy
@bound_json
#
# @overload val()
# Get this as a Json object.
# @return [Json]
#
# @overload val(name)
# Get value of a property.
# @param {String} name Name of the object property.
# @return [Object Type||String|Object] Depending on the value of the property. If mutable it will return a Operation-type object, if immutable it will return String/Object.
#
# @overload val(name, content)
# Set a new property.
# @param {String} name Name of the object property.
# @param {Object|String} content Content of the object property.
# @return [Object Type] This object. (supports chaining)
#
val: (name, content)->
if name? and arguments.length > 1
if content? and content.constructor?
type = ops[content.constructor.name]
if type? and type.create?
args = []
for i in [1...arguments.length]
args.push arguments[i]
o = type.create.apply null, args
super name, o
else
throw new Error "The #{content.constructor.name}-type is not (yet) supported in Y."
else
super name, content
else # is this even necessary ? I have to define every type anyway.. (see Number type below)
super name
#
# @private
#
_encode: ()->
{
'type' : @type
'uid' : @getUid()
}
ops.Object.parse = (json)->
{
'uid' : uid
} = json
new this(uid)
ops.Object.create = (content, mutable)->
json = new ops.Object().execute()
for n,o of content
json.val n, o, mutable
json
ops.Number = {}
ops.Number.create = (content)->
content
text_ops

View File

@ -13,46 +13,65 @@ module.exports = ()->
#
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
#
constructor: (uid)->
@map = {}
constructor: (custom_type, uid)->
if custom_type?
@custom_type = custom_type
@_map = {}
super uid
type: "MapManager"
applyDelete: ()->
for name,p of @map
for name,p of @_map
p.applyDelete()
super()
cleanup: ()->
super()
map: (f)->
for n,v of @_map
f(n,v)
undefined
#
# @see JsonOperations.val
#
val: (name, content)->
if arguments.length > 1
@retrieveSub(name).replace content
if content? and content._model? and content._model instanceof ops.Operation
rep = content._model
else
rep = content
@retrieveSub(name).replace rep
@
else if name?
prop = @map[name]
prop = @_map[name]
if prop? and not prop.isContentDeleted()
prop.val()
res = prop.val()
if res instanceof ops.Operation
res.getCustomType()
else
res
else
undefined
else
result = {}
for name,o of @map
for name,o of @_map
if not o.isContentDeleted()
result[name] = o.val()
res = prop.val()
if res instanceof ops.Operation
result[name] = res.getCustomType()
else
result[name] = res
result
delete: (name)->
@map[name]?.deleteContent()
@_map[name]?.deleteContent()
@
retrieveSub: (property_name)->
if not @map[property_name]?
if not @_map[property_name]?
event_properties =
name: property_name
event_this = @
@ -61,10 +80,33 @@ module.exports = ()->
sub: property_name
alt: @
rm = new ops.ReplaceManager event_properties, event_this, rm_uid # this operation shall not be saved in the HB
@map[property_name] = rm
@_map[property_name] = rm
rm.setParent @, property_name
rm.execute()
@map[property_name]
@_map[property_name]
#
# @private
#
_encode: ()->
json = {
'type' : @type
'uid' : @getUid()
}
if @custom_type.constructor is String
json.custom_type = @custom_type
else
json.custom_type = @custom_type._name
json
ops.MapManager.parse = (json)->
{
'uid' : uid
'custom_type' : custom_type
} = json
new this(custom_type, uid)
#
# @nodoc
@ -393,7 +435,10 @@ module.exports = ()->
# Return the content that this operation holds.
#
val: ()->
@content
if @content? and @content.getCustomType?
@content.getCustomType()
else
@content
applyDelete: ()->
res = super

73
lib/Types/Object.coffee Normal file
View File

@ -0,0 +1,73 @@
class YObject
constructor: (@_object = {})->
if @_object.constructor is Object
for name, val of @_object
if val.constructor is Object
@_object[name] = new YObject(val)
else
throw new Error "Y.Object accepts Json Objects only"
_name: "Object"
_getModel: (types, ops)->
if not @_model?
@_model = new ops.MapManager(@).execute()
for n,o of @_object
@_model.val n, o
delete @_object
@_model
_setModel: (@_model)->
delete @_object
observe: (f)->
@_model.observe f
#
# @overload val()
# Get this as a Json object.
# @return [Json]
#
# @overload val(name)
# Get value of a property.
# @param {String} name Name of the object property.
# @return [Object Type||String|Object] Depending on the value of the property. If mutable it will return a Operation-type object, if immutable it will return String/Object.
#
# @overload val(name, content)
# Set a new property.
# @param {String} name Name of the object property.
# @param {Object|String} content Content of the object property.
# @return [Object Type] This object. (supports chaining)
#
val: (name, content)->
if @_model?
@_model.val.apply @_model, arguments
else
if content?
@_object[name] = content
else if name?
@_object[name]
else
res = {}
for n,v of @_object
res[n] = v
res
if window?
if window.Y?
window.Y.Object = YObject
else
throw new Error "You must first import Y!"
if module?
module.exports = YObject

View File

@ -1,5 +1,5 @@
json_ops_uninitialized = require "./Operations/Json"
text_ops_uninitialized = require "./Operations/Text"
HistoryBuffer = require "./HistoryBuffer"
Engine = require "./Engine"
@ -15,7 +15,7 @@ createY = (connector)->
user_id = id
HB.resetUserId id
HB = new HistoryBuffer user_id
ops_manager = json_ops_uninitialized HB, this.constructor
ops_manager = text_ops_uninitialized HB, this.constructor
ops = ops_manager.operations
engine = new Engine HB, ops
@ -27,8 +27,13 @@ createY = (connector)->
ops.Operation.prototype.connector = connector
ops.Operation.prototype.custom_ops = this.constructor
return new ops.Object(HB.getReservedUniqueIdentifier()).execute()
ct = new createY.Object()
model = new ops.MapManager(ct, HB.getReservedUniqueIdentifier()).execute()
ct._setModel model
ct
module.exports = createY
if window? and not window.Y?
if window?
window.Y = createY
createY.Object = require "./Types/Object"

View File

@ -10,6 +10,18 @@ chai.use(sinonChai)
Connector = require "../../y-test/lib/y-test.coffee"
Y = require "../lib/y.coffee"
compare = (o1, o2)->
if o1.type? and o1.type isnt o2.type
throw new Error "different types"
else if o1.type is "Object"
for name, val of o1.val()
compare(val, o2.val(name))
else if o1.type?
compare(o1.val(), o2.val())
else if o1 isnt o2
throw new Error "different values"
Test = require "./TestSuite"
class JsonTest extends Test
@ -43,12 +55,8 @@ class JsonTest extends Test
p = elems[_.random(0, elems.length-1)]
@getRandomRoot user_num, p
getContent: (user_num)->
@users[user_num].toJson(true)
getGeneratingFunctions: (user_num)->
types = @users[user_num].operations
types = @users[user_num]._model.operations
super(user_num).concat [
f : (y)=> # SET PROPERTY
l = y.val().length
@ -113,18 +121,8 @@ describe "JsonFramework", ->
ops2 = u2.HB._encode()
u1.engine.applyOp ops2, true
u2.engine.applyOp ops1, true
compare = (o1, o2)->
if o1.type? and o1.type isnt o2.type
throw new Error "different types"
else if o1.type is "Object"
for name, val of o1.val()
compare(val, o2.val(name))
else if o1.type?
compare(o1.val(), o2.val())
else if o1 isnt o2
throw new Error "different values"
compare u1, u2
expect(test.getContent(0)).to.deep.equal(@yTest.getContent(1))
expect(compare(u1, u2)).to.not.be.undefined
it "can handle creaton of complex json (1)", ->
@yTest.users[0].val('a', 'q', "mutable")
@ -136,16 +134,16 @@ describe "JsonFramework", ->
expect(@yTest.getSomeUser().val("a").val()).to.equal("At")
it "can handle creaton of complex json (2)", ->
@yTest.getSomeUser().val('x', {'a':'b'})
@yTest.getSomeUser().val('a', {'a':{q:"dtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt"}}, "mutable")
@yTest.getSomeUser().val('b', {'a':{}})
@yTest.getSomeUser().val('c', {'a':'c'})
@yTest.getSomeUser().val('c', {'a':'b'})
@yTest.getSomeUser().val('x', new Y.Object({'a':'b'}))
@yTest.getSomeUser().val('a', new Y.Object({'a':{q:"dtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt"}}))
@yTest.getSomeUser().val('b', new Y.Object({'a':{}}))
@yTest.getSomeUser().val('c', new Y.Object({'a':'c'}))
@yTest.getSomeUser().val('c', new Y.Object({'a':'b'}))
@yTest.compareAll()
q = @yTest.getSomeUser().val("a").val("a").val("q")
q.insert(0,'A')
@yTest.compareAll()
expect(@yTest.getSomeUser().val("a").val("a").val("q").val()).to.equal("Adtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt")
expect(@yTest.getSomeUser().val("a").val("a").val("q")).to.equal("Adtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt")
it "can handle creaton of complex json (3)", ->
@yTest.users[0].val('l', [1,2,3], "mutable")
@ -171,7 +169,7 @@ describe "JsonFramework", ->
it "handles immutables and primitive data types (2)", ->
@yTest.users[0].val('string', "text", "immutable")
@yTest.users[1].val('number', 4, "immutable")
@yTest.users[2].val('object', {q:"rr"}, "immutable")
@yTest.users[2].val('object', {q:"rr"})
@yTest.users[0].val('null', null)
@yTest.compareAll()
expect(@yTest.getSomeUser().val('string')).to.equal "text"

View File

@ -28,14 +28,14 @@ module.exports = class Test
for i in [0...@number_of_engines]
u = @makeNewUser (i+@name_suffix)
for user in @users
u.connector.join(user.connector) # TODO: change the test-connector to make this more convenient
u._model.connector.join(user._model.connector) # TODO: change the test-connector to make this more convenient
@users.push u
@initUsers?(@users[0])
@flushAll()
# is called by implementing class
makeNewUser: (user)->
user.HB.stopGarbageCollection()
user._model.HB.stopGarbageCollection()
user
getSomeUser: ()->
@ -69,7 +69,7 @@ module.exports = class Test
@getRandomText [1,2,'x','y'], 1 # only 4 keys
getGeneratingFunctions: (user_num)=>
types = @users[user_num].operations
types = @users[user_num]._model.operations
[
f : (y)=> # INSERT TEXT
y
@ -106,7 +106,7 @@ module.exports = class Test
applyRandomOp: (user_num)=>
user = @users[user_num]
user.connector.flushOneRandom()
user._model.connector.flushOneRandom()
doSomething: ()->
user_num = _.random (@number_of_engines-1)
@ -119,15 +119,14 @@ module.exports = class Test
final = false
if @users.length <= 1 or not final
for user,user_number in @users
user.connector.flushAll()
user._model.connector.flushAll()
else
for user,user_number in @users[1..]
user.connector.flushAll()
user._model.connector.flushAll()
ops = @users[1].getHistoryBuffer()._encode @users[0].HB.getOperationCounter()
@users[0].engine.applyOpsCheckDouble ops
compareAll: (test_number)->
@flushAll(true)
@ -135,7 +134,7 @@ module.exports = class Test
number_of_created_operations = 0
for i in [0...(@users.length)]
number_of_created_operations += @users[i].connector.getOpsInExecutionOrder().length
number_of_created_operations += @users[i]._model.connector.getOpsInExecutionOrder().length
@ops += number_of_created_operations*@users.length
ops_per_msek = Math.floor(@ops/@time)
@ -146,7 +145,7 @@ module.exports = class Test
if @debug
if not _.isEqual @getContent(i), @getContent(i+1)
printOpsInExecutionOrder = (otnumber, otherotnumber)=>
ops = _.filter @users[otnumber].connector.getOpsInExecutionOrder(), (o)->
ops = _.filter @users[otnumber]._model.connector.getOpsInExecutionOrder(), (o)->
typeof o.uid.op_name isnt 'string' and o.uid.creator isnt '_'
for s,j in ops
console.log "op#{j} = " + (JSON.stringify s)

File diff suppressed because one or more lines are too long

4
y.js

File diff suppressed because one or more lines are too long