Changeset View
Standalone View
binaries/data/mods/public/simulation/helpers/MultiKeyMap.js
- This file was added.
// Convenient container abstraction for storing items referenced by a 3-tuple. | |||||
// Used by the itemsManager to store items by (property Name, entity, item ID). | |||||
// Methods starting with an underscore are private to the storage. | |||||
// This supports stackable items as it stores count for each 3-tuple. | |||||
function MultiKeyMap() | |||||
{ | |||||
this.items = new Map(); | |||||
// Keys are referred to as 'primaryKey', 'secondaryKey', 'itemID'. | |||||
}; | |||||
MultiKeyMap.prototype.Serialize = function() | |||||
{ | |||||
let ret = {}; | |||||
for (let primary of this.items.keys()) | |||||
{ | |||||
ret[primary] = {}; | |||||
for (let secondary of this.items.get(primary).keys()) | |||||
ret[primary][secondary] = this.items.get(primary).get(secondary); | |||||
} | |||||
return ret; | |||||
}; | |||||
MultiKeyMap.prototype.Deserialize = function(data) | |||||
{ | |||||
for (let primary in data) | |||||
{ | |||||
this.items.set(primary, new Map()); | |||||
for (let secondary in data[primary]) | |||||
this.items.get(primary).set(secondary, data[primary][secondary]); | |||||
} | |||||
}; | |||||
/** | |||||
* Add a single item. | |||||
elexis: Doesn't native Map, array, object serialization and deserialization work already? | |||||
* NB: if you add an item with a different value but the same itemID, the original value remains. | |||||
* @param itemID - internal ID of this item, for later removal and/or updating | |||||
* @param stackable - if stackable, changing the count of items invalides, otherwise not. | |||||
* @returns true if the items list changed in such a way that cached values are possibly invalidated. | |||||
*/ | |||||
MultiKeyMap.prototype.AddItem = function(primaryKey, itemID, item, secondaryKey, stackable = false) | |||||
{ | |||||
if (!this._AddItem(primaryKey, itemID, item, secondaryKey, stackable)) | |||||
return false; | |||||
this._OnItemModified(primaryKey, secondaryKey, itemID); | |||||
return true; | |||||
}; | |||||
/** | |||||
* Add items to multiple properties at once (only one item per property) | |||||
* @param items - Dictionnary of { primaryKey: item } | |||||
* @returns true if the items list changed in such a way that cached values are possibly invalidated. | |||||
*/ | |||||
MultiKeyMap.prototype.AddItems = function(itemID, items, secondaryKey, stackable = false) | |||||
{ | |||||
let modified = false; | |||||
for (let primaryKey in items) | |||||
modified = this.AddItem(primaryKey, itemID, items[primaryKey], secondaryKey, stackable) || modified; | |||||
return modified; | |||||
}; | |||||
/** | |||||
* Removes a item on a property. | |||||
Not Done Inline Actionsif this.AddItem return true, avoiding assignment, || operation and variable elexis: if `this.AddItem` return true, avoiding assignment, || operation and variable | |||||
* @param primaryKey - property to change (e.g. "Health/Max") | |||||
* @param itemID - internal ID of the item to remove | |||||
* @param secondaryKey - secondaryKey ID | |||||
* @returns true if the items list changed in such a way that cached values are possibly invalidated. | |||||
*/ | |||||
MultiKeyMap.prototype.RemoveItem = function(primaryKey, itemID, secondaryKey, stackable = false) | |||||
{ | |||||
if (!this._RemoveItem(primaryKey, itemID, secondaryKey, stackable)) | |||||
return false; | |||||
this._OnItemModified(primaryKey, secondaryKey, itemID); | |||||
return true; | |||||
}; | |||||
/** | |||||
* Removes items with this ID for any property name. | |||||
* Naively iterates all property names. | |||||
* @returns true if the items list changed in such a way that cached values are possibly invalidated. | |||||
*/ | |||||
MultiKeyMap.prototype.RemoveAllItems = function(itemID, secondaryKey, stackable = false) | |||||
{ | |||||
let modified = false; | |||||
// Map doesn't implement some so use a for-loop here. | |||||
for (let primaryKey of this.items.keys()) | |||||
modified = this.RemoveItem(primaryKey, itemID, secondaryKey, stackable) || modified; | |||||
return modified; | |||||
}; | |||||
/** | |||||
* @param itemID - internal ID of the item to try and find. | |||||
* @returns true if there is at least one item with that itemID | |||||
*/ | |||||
MultiKeyMap.prototype.HasItem = function(primaryKey, itemID, secondaryKey) | |||||
{ | |||||
// some() returns false for an empty list which is wanted here. | |||||
return this._getItems(primaryKey, secondaryKey).some(item => item.ID === itemID); | |||||
}; | |||||
/** | |||||
* Check if we have a item for any property name. | |||||
* Naively iterates all property names. | |||||
* @returns true if there is at least one item with that itemID | |||||
*/ | |||||
MultiKeyMap.prototype.HasAnyItem = function(itemID, secondaryKey) | |||||
{ | |||||
// Map doesn't implement some so use for loops instead. | |||||
for (let primaryKey of this.items.keys()) | |||||
if (this.HasItem(primaryKey, itemID, secondaryKey)) | |||||
return true; | |||||
return false; | |||||
}; | |||||
/** | |||||
* @returns A list of items with storage metadata removed. | |||||
*/ | |||||
MultiKeyMap.prototype.GetItems = function(primaryKey, secondaryKey, stackable = false) | |||||
{ | |||||
let items = []; | |||||
if (stackable) | |||||
this._getItems(primaryKey, secondaryKey).forEach(item => items = items.concat(Array(item.count).fill(item.value))); | |||||
else | |||||
this._getItems(primaryKey, secondaryKey).forEach(item => items.push(item.value)); | |||||
return items; | |||||
}; | |||||
Not Done Inline Actionsmissing semicolon for items = items.concat... and items.push(item.value), because it is not the intention to return the result of the assignment or the result of the push call, but to only perform this as one statement; i.e. foo => {\n this._getItems(primaryKey, secondaryKey).forEach( stackable ? item => { items = items.concat(Array(item.count).fill(item.value)))) }, item => { items.push(item.value); } But looking again, it seems the stackable case could use reduce elexis: missing semicolon for `items = items.concat...` and `items.push(item.value)`, because it is… | |||||
/** | |||||
* @returns A dictionary of { Property Name: items } for the secondary Key. | |||||
* Naively iterates all property names. | |||||
*/ | |||||
MultiKeyMap.prototype.GetAllItems = function(secondaryKey, stackable = false) | |||||
{ | |||||
let items = {}; | |||||
// Map doesn't implement filter so use a for loop. | |||||
for (let primaryKey of this.items.keys()) | |||||
{ | |||||
if (!this.items.get(primaryKey).has(secondaryKey)) | |||||
continue; | |||||
Not Done Inline Actionsis forEach faster than getting all keys() and then processing them? (as its one array construction less unless optimized) elexis: is `forEach` faster than getting all `keys()` and then processing them? (as its one array… | |||||
items[primaryKey] = this.GetItems(primaryKey, secondaryKey, stackable); | |||||
} | |||||
return items; | |||||
}; | |||||
Not Done Inline Actions(perhaps negate condition and remove continue) elexis: (perhaps negate condition and remove continue) | |||||
/** | |||||
* @returns a list of items. | |||||
* This does not necessarily return a reference to items' list, use _getItemsOrInit for that. | |||||
*/ | |||||
MultiKeyMap.prototype._getItems = function(primaryKey, secondaryKey) | |||||
{ | |||||
if (!this._exists(primaryKey, secondaryKey)) | |||||
return []; | |||||
return this.items.get(primaryKey).get(secondaryKey); | |||||
} | |||||
/** | |||||
* @returns a reference to the list of items for that property name and secondaryKey. | |||||
*/ | |||||
MultiKeyMap.prototype._getItemsOrInit = function(primaryKey, secondaryKey) | |||||
{ | |||||
if (!this._exists(primaryKey, secondaryKey)) | |||||
this._initItemsIfNeeded(primaryKey, secondaryKey); | |||||
return this.items.get(primaryKey).get(secondaryKey); | |||||
} | |||||
MultiKeyMap.prototype._exists = function(primaryKey, secondaryKey) | |||||
{ | |||||
if (!this.items.get(primaryKey)) | |||||
return false; | |||||
if (!this.items.get(primaryKey).get(secondaryKey)) | |||||
return false; | |||||
return true; | |||||
} | |||||
MultiKeyMap.prototype._initItemsIfNeeded = function(primaryKey, secondaryKey) | |||||
{ | |||||
Not Done Inline Actionsreturn this.items.has(primaryKey) && this.items.get(primaryKey).has(secondaryKey)? elexis: `return this.items.has(primaryKey) && this.items.get(primaryKey).has(secondaryKey)`? | |||||
if (!this.items.get(primaryKey)) | |||||
this.items.set(primaryKey, new Map()); | |||||
if (!this.items.get(primaryKey).get(secondaryKey)) | |||||
this.items.get(primaryKey).set(secondaryKey, []); | |||||
} | |||||
/** | |||||
* @returns true if the items list changed in such a way that cached values are possibly invalidated. | |||||
*/ | |||||
MultiKeyMap.prototype._AddItem = function(primaryKey, itemID, item, secondaryKey, stackable) | |||||
{ | |||||
let items = this._getItemsOrInit(primaryKey, secondaryKey); | |||||
let existingItems = items.filter(item => { return item.ID == itemID; }); | |||||
if (existingItems.length) | |||||
{ | |||||
existingItems[0].count++; | |||||
Not Done Inline Actionsitem => item.ID == itemID elexis: `item => item.ID == itemID` | |||||
return stackable; | |||||
} | |||||
Not Done Inline Actions++foo unless return value matters, according to the CC elexis: ++foo unless return value matters, according to the CC | |||||
items.push({ "ID": itemID, "count": 1, "value": item }); | |||||
return true; | |||||
}; | |||||
/** | |||||
* @returns true if the items list changed in such a way that cached values are possibly invalidated. | |||||
*/ | |||||
MultiKeyMap.prototype._RemoveItem = function(primaryKey, itemID, secondaryKey, stackable) | |||||
{ | |||||
let items = this._getItems(primaryKey, secondaryKey); | |||||
let existingItem = items.filter(item => { return item.ID == itemID; }); | |||||
if (!existingItem.length) | |||||
return false; | |||||
Not Done Inline Actionssame elexis: same | |||||
if (--existingItem[0].count > 0) | |||||
return stackable; | |||||
let stilValidItems = items.filter(item => item.count > 0); | |||||
// Delete entries from the map if necessary to clean up. | |||||
if (!stilValidItems.length) | |||||
{ | |||||
this.items.get(primaryKey).delete(secondaryKey); | |||||
if (!this.items.get(primaryKey).size) | |||||
this.items.delete(primaryKey); | |||||
return true; | |||||
} | |||||
this.items.get(primaryKey).set(secondaryKey, stilValidItems); | |||||
return true; | |||||
}; | |||||
/** | |||||
* Stub method, to overload. | |||||
*/ | |||||
MultiKeyMap.prototype._OnItemModified = function(primaryKey, secondaryKey, itemID) {}; | |||||
Engine.RegisterGlobal("MultiKeyMap", MultiKeyMap); |
Doesn't native Map, array, object serialization and deserialization work already?