0ad/binaries/data/mods/public/globalscripts/MultiKeyMap.js
Freagarach 9450cfcbda Move custom JS data structures to globalscripts.
These don't depend on the simulation and can be useful for e.g. the GUI
(the FSM) or map scripts.
Confer also the vector implementation.

Differential revision: D3863
Comment by: @wraitii (agreed with concept)
This was SVN commit r25288.
2021-04-18 08:31:30 +00:00

224 lines
6.9 KiB
JavaScript

// Convenient container abstraction for storing items referenced by a 3-tuple.
// Used by the ModifiersManager 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.
// It is designed to be as fast as can be for a JS container.
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())
{
// Keys of a Map can be arbitrary types whereas objects only support string, so use a list.
let vals = [primary, []];
ret.push(vals);
for (let secondary of this.items.get(primary).keys())
vals[1].push([secondary, this.items.get(primary).get(secondary)]);
}
return ret;
};
MultiKeyMap.prototype.Deserialize = function(data)
{
for (let primary in data)
{
this.items.set(data[primary][0], new Map());
for (let secondary in data[primary][1])
this.items.get(data[primary][0]).set(data[primary][1][secondary][0], data[primary][1][secondary][1]);
}
};
/**
* Add a single item.
* NB: if you add an item with a different value but the same itemID, the original value remains.
* @param item - an object.
* @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.
* @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 (references to stored items to avoid copying)
* (these need to be treated as constants to not break the map)
*/
MultiKeyMap.prototype.GetItems = function(primaryKey, secondaryKey)
{
return this._getItems(primaryKey, secondaryKey);
};
/**
* @returns A dictionary of { Property Name: items } for the secondary Key.
* Naively iterates all property names.
*/
MultiKeyMap.prototype.GetAllItems = function(secondaryKey)
{
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;
items[primaryKey] = this.GetItems(primaryKey, secondaryKey);
}
return items;
};
/**
* @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)
{
let cache = this.items.get(primaryKey);
if (cache)
cache = cache.get(secondaryKey);
return cache ? cache : [];
};
/**
* @returns a reference to the list of items for that property name and secondaryKey.
*/
MultiKeyMap.prototype._getItemsOrInit = function(primaryKey, secondaryKey)
{
let cache = this.items.get(primaryKey);
if (!cache)
cache = this.items.set(primaryKey, new Map()).get(primaryKey);
let cache2 = cache.get(secondaryKey);
if (!cache2)
cache2 = cache.set(secondaryKey, []).get(secondaryKey);
return cache2;
};
/**
* @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);
for (let it of items)
if (it._ID == itemID)
{
it._count++;
return stackable;
}
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;
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) {};