**Croquet and Morphic.js** # Introduction This is a simple starting point to think about how morphic.js would be powered by Croquet to make it multiplayer. One of fundamental ideas of this reincarnation of Croquet is strong separation between the "model" and the "view". # Thinking about properties In Morph.init, it has following property (along with two properties from Node): ~~~~~~~~~~~~~~~~~~~~~~~ JavaScript this.parent; this.children; this.isMorph = true; // used to optimize deep copying this.cachedImage = null; this.isCachingImage = false; this.shouldRerender = false; this.bounds = new Rectangle(0, 0, 50, 40); this.holes = []; // list of "untouchable" regions (rectangles) this.color = new Color(80, 80, 80); this.texture = null; // optional url of a fill-image this.cachedTexture = null; // internal cache of actual bg image this.alpha = 1; this.isVisible = true; this.isDraggable = false; this.isTemplate = false; this.acceptsDrops = false; this.isFreeForm = false; this.noDropShadow = false; this.fullShadowSource = true; this.fps = 0; this.customContextMenu = null; this.lastTime = Date.now(); this.onNextStep = null; // optional function to be run once ~~~~~~~~~~~~~~~~~~~~~~~ Basically, to represent an equivalent of Morph object in Croquet's regime, there would be an instance of model side of Morph and an instance of view side class MorphView. A natural way to start thinking about this is how to splitting these existing properties into the model and the view. It can be summarized as: "anything that can be synthesized from the properties in the model should be in the view" provided that "only things that should be in the model is something that can be serialized (by the croquet serializer that can handle cycles)". An important point here is that different users have different window pixel counts. If a coordinate system based on CSS pixel is to be used for the logical model specification, either the view would have to scale the visual appearances of the screen, or each screen part (scripting area, palette, stage, etc.) would have independent CSS coordinates and the view on each user's computer lays out them. In the latter case, the actual screen layout might be different for each user. There can be other approaches. I did fully identify the object types that are stored in those properties, but as far as I can guess from the property names, the model properties would be: ~~~~~~~~~~~~~~~~~~~~~~~ JavaScript this.parent; // can be a direct pointer to another model object this.children; // can be direct pointers to other model objects this.isMorph; this.isCachingImage; this.shouldRerender; // I may be mistaken what those two properties are this.bounds; // again, what the pixel based coordinates means is to be debated this.holes; // the model holds onto it and the view sees it to determine pointer hit this.color; this.alpha = 1; this.isVisible = true; this.isDraggable = false; this.isTemplate = false; this.acceptsDrops = false; this.isFreeForm = false; this.noDropShadow = false; this.fullShadowSource = true; // above properties are still in the model, // but the view uses it to change its rendering // and user interaction behavior this.fps = 0; // ticking is done differently but the concept of rate is in the model this.lastTime = Date.now(); this.onNextStep = null; // optional function to be run once this.customContextMenu = null; // can be a pointer to a model object ~~~~~~~~~~~~~~~~~~~~~~~ # Model Methods Let us sketch out some typical model methods. Methods for pure model manipulation do not have to be changed at all. ~~~~~~~~~~~~~~~~~~~~~~~ JavaScript Morph.prototype.destroy = function () { if (this.parent !== null) { this.fullChanged(); this.parent.removeChild(this); } }; ~~~~~~~~~~~~~~~~~~~~~~~ However, the implementation of `fullChanged()` and its view side counterpart (that takes over most responsibility of `WorldMorph`) would be the main consideration. Also methods like: ~~~~~~~~~~~~~~~~~~~~~~~ JavaScript Morph.prototype.scrollIntoView Morph.prototype.setExtent ~~~~~~~~~~~~~~~~~~~~~~~ are all model side. `fixLayout()` would be a model side method and sets bounds for all model objects involved. # View Methods The MorphView class can be instantiated from your program when a `Morph` is created. One of the nice things about Croquet is that the view object can read data out of the model object (it equates to querying data from the server if the server is the only place where the "true" value is stored). The model object is passed in to the view's constructor, and the view con holds onto it. So MorphView's init would look like: ~~~~~~~~~~~~~~~~~~~~~~~ JavaScript MorphView.prototype.init = function (model) { this.model; this.cachedImage = null; this.texture = null; this.cachedTexture = null; }; ~~~~~~~~~~~~~~~~~~~~~~~ A method like `getImage()` would belong to `MorphView`. It basic structure does not have to change, execpt that some properites are read from the model.: ~~~~~~~~~~~~~~~~~~~~~~~ JavaScript MorphView.prototype.getImage = function () { var img; if (this.cachedImage && !this.model.shouldRerender) { // notice this.model return this.cachedImage; } img = newCanvas(this.extent(), false, this.cachedImage); if (this.model.isCachingImage) { // notice this.model this.cachedImage = img; } this.render(img.getContext('2d')); // this.shouldRerender = false; This is another consideration. a view cannot write into the model return img; }; ~~~~~~~~~~~~~~~~~~~~~~~ # WorldMorph `WorldMorph` would be split into `WorldMorph` and `WorldMorphView`. This one (or ones) is the bridge between the model side and the view side. The model side WorldMorph would hold properties: ~~~~~~~~~~~~~~~~~~~~~~~ JavaScript WorldMorph.prototype.init = function (fillPage) { WorldMorph.uber.init.call(this); this.color = new Color(205, 205, 205); this.alpha = 1; this.bounds = new Rectangle(...); // here it is a question of the overall design this.isVisible = true; this.isDraggable = false; this.currentKey = null; // currently pressed key code // additional properties: this.stamp = Date.now(); // reference in multi-world setups while (this.stamp === Date.now()) {nop(); } this.stamp = Date.now(); // I don't know them this.useFillPage = fillPage; if (this.useFillPage === undefined) { this.useFillPage = true; } this.isDevMode = false; this.broken = []; this.animations = []; this.hands = [new HandMorph(this)]; // here the "hands" issue need to be addressed this.keyboardHandler = null; // ditto this.keyboardFocus = null; // ditto this.cursor = null; // ditto this.lastEditedText = null; // ditto this.activeMenu = null; // ditto this.activeHandle = null; // ditto }; ~~~~~~~~~~~~~~~~~~~~~~~ The WorldMorphView would look like: ~~~~~~~~~~~~~~~~~~~~~~~ JavaScript WorldMorphView.prototype.init = function (model) { let aCanvas = getTheCanvas(); this.hands = [new HandMorphView(this)]; // perhaps... this.keyboardHandler = null; // this.keyboardFocus = null; // this.cursor = null; // this.lastEditedText = null; // this.activeMenu = null; // this.activeHandle = null; // this.initKeyboardHandler(); this.resetKeyboardHandler(); this.initEventListeners(); }; ~~~~~~~~~~~~~~~~~~~~~~~ User event handlers have to be on the view side. When it receives a user event on one computer, First if the event is routed in the structure of the model (again, the all Morph are available directly as read-only object), determine which view of the morph should handle the event. The view then decides whether it is a view side action (see "Menus and Buttons and likes" below) and it just invokes the view side effects, or publishes that as an event to the model. In the latter case, all replicated participants receive the event, execute appropriate model side mutations. The mutation would change the "shouldRender" property, and when the WorldMorph decides to render the new graphics the updated state is used. # Menus and Buttons and likes Things like Menus and Buttons require special consideration. Sometimes you want such a menu to have some shared effect so that any participating user pressing a button would cause some effects to all participants. But some other times, such button pressing would only cause some view side change for the particular user. The implementation of the menu and button would basically have to have a flag. In general, how does one morph trigger actions on other objects, either on the model side or view side? The low-level interface of Croquet provides a unified publish/subscribe mechanism for inter-object communication. So `trigger()` would probably use croquet/publish subscribe, or some thin layout on top of it.