if (!Function.prototype.bind) { Function.prototype.bind = function(oThis) { if (typeof this !== 'function') { // closest thing possible to the ECMAScript 5 // internal IsCallable function throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); } var aArgs = Array.prototype.slice.call(arguments, 1), fToBind = this, fNOP = function() {}, fBound = function() { return fToBind.apply(this instanceof fNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); }; if (this.prototype) { // Function.prototype doesn't have a prototype property fNOP.prototype = this.prototype; } fBound.prototype = new fNOP(); return fBound; }; } function getOption(options, key, defaultValue) { if (options.hasOwnProperty(key)) { return options[key]; } return defaultValue; } var TOKEN_NAME_PREFIX = "ownership_token-"; function getOwnershipTokenID(parentEntityID) { var childEntityIDs = Entities.getChildrenIDs(parentEntityID); var ownerID = null; var ownerName = ''; for (var i = 0; i < childEntityIDs.length; ++i) { var childID = childEntityIDs[i]; var properties = Entities.getEntityProperties(childID, ['name', 'userData', 'lifetime', 'age']); var childName = properties.name; if (childName.indexOf(TOKEN_NAME_PREFIX) == 0) { if (ownerID === null || childName < ownerName) { ownerID = childID; ownerName = childName; } } } return ownerID; } function createOwnershipToken(name, parentEntityID) { return Entities.addEntity({ type: "Box", name: TOKEN_NAME_PREFIX + name, visible: false, parentID: parentEntityID, locationPosition: { x: 0, y: 0, z: 0 }, dimensions: { x: 100, y: 100, z: 100 }, collisionless: true, lifetime: 5 }); } var DEBUG = true; function debug() { if (DEBUG) { var args = Array.prototype.slice.call(arguments); print.apply(this, args); } } var TOKEN_STATE_DESTROYED = -1; var TOKEN_STATE_UNOWNED = 0; var TOKEN_STATE_REQUESTING_OWNERSHIP = 1; var TOKEN_STATE_OWNED = 2; OwnershipToken = function(name, parentEntityID, options) { this.name = MyAvatar.sessionUUID + "-" + Math.floor(Math.random() * 10000000); this.parentEntityID = parentEntityID; // How often to check whether the token is available if we don't currently own it this.checkEverySeconds = getOption(options, 'checkEverySeconds', 1000); this.updateTokenLifetimeEvery = getOption(options, 'updateTokenLifetimeEvery', 2000); this.onGainedOwnership = getOption(options, 'onGainedOwnership', function() { }); this.onLostOwnership = getOption(options, 'onLostOwnership', function() { }); this.ownershipTokenID = null; this.setState(TOKEN_STATE_UNOWNED); }; OwnershipToken.prototype = { destroy: function() { debug(this.name, "Destroying token"); this.setState(TOKEN_STATE_DESTROYED); }, setState: function(newState) { if (this.state == newState) { debug(this.name, "Warning: Trying to set state to the current state"); return; } if (this.updateLifetimeID) { debug(this.name, "Clearing update lifetime interval"); Script.clearInterval(this.updateLifetimeID); this.updateLifetimeID = null; } if (this.checkOwnershipAvailableID) { Script.clearInterval(this.checkOwnershipAvailableID); this.checkOwnershipAvailableID = null; } if (this.state == TOKEN_STATE_OWNED) { this.onLostOwnership(this); } if (newState == TOKEN_STATE_UNOWNED) { this.checkOwnershipAvailableID = Script.setInterval( this.tryRequestingOwnership.bind(this), this.checkEverySeconds); } else if (newState == TOKEN_STATE_REQUESTING_OWNERSHIP) { } else if (newState == TOKEN_STATE_OWNED) { this.onGainedOwnership(this); this.updateLifetimeID = Script.setInterval( this.updateTokenLifetime.bind(this), this.updateTokenLifetimeEvery); } else if (newState == TOKEN_STATE_DESTROYED) { Entities.deleteEntity(this.ownershipTokenID); } debug(this.name, "Info: Switching to state:", newState); this.state = newState; }, updateTokenLifetime: function() { if (this.state != TOKEN_STATE_OWNED) { debug(this.name, "Error: Trying to update token while it is unowned"); return; } debug(this.name, "Updating entity lifetime"); var age = Entities.getEntityProperties(this.ownershipTokenID, 'age').age; Entities.editEntity(this.ownershipTokenID, { lifetime: age + 5 }); }, tryRequestingOwnership: function() { if (this.state == TOKEN_STATE_REQUESTING_OWNERSHIP || this.state == TOKEN_STATE_OWNED) { debug(this.name, "We already have or are requesting ownership"); return; } var ownerID = getOwnershipTokenID(this.parentEntityID); if (ownerID !== null) { // Already owned, return debug(this.name, "Token already owned by another client, returning. Owner: " + owenerID + ", Us: " + this.name); return; } this.ownershipTokenID = createOwnershipToken(this.name, this.parentEntityID); this.setState(TOKEN_STATE_REQUESTING_OWNERSHIP); function checkOwnershipRequest() { var ownerID = getOwnershipTokenID(this.parentEntityID); if (ownerID == this.ownershipTokenID) { debug(this.name, "Info: Obtained ownership"); this.setState(TOKEN_STATE_OWNED); } else { if (ownerID === null) { debug(this.name, "Warning: Checked ownership request and no tokens existed"); } debug(this.name, "Info: Lost ownership request") this.ownershipTokenID = null; this.setState(TOKEN_STATE_UNOWNED); } } Script.setTimeout(checkOwnershipRequest.bind(this), 2000); }, }; debug("Returning from ownershipToken");