monoidal_effects/effect_set.lua
2016-01-23 15:21:56 -08:00

407 lines
7.5 KiB
Lua

-- Defines the relational data structure used for storing status effects.
--
--
-- Effects
--
-- A single effect record has these components:
--
-- A boolean, whether it is dynamic (dynamic)
--
-- The effect type name (effect_type)
--
-- A set of names of players it applies to (players)
--
-- A set of tags it has (tags)
--
-- A set of monoids it belongs to (monoids)
--
-- A table from monoid names to monoidal values (values), if it is dynamic
--
-- A duration (duration), either the string "perm" or a number of seconds
--
--
-- Effect Set
--
-- Has a table uid_table that maps uids to records
--
-- Has a field "tables" that holds all the index tables.
--
-- Currently, it has index tables:
--
-- player (many-to-many)
-- tag (many-to-many)
-- monoid (many-to-many)
-- name (one-to-many)
-- perm (one-to-many)
--
-- To serialize, it suffices to just serialize the ID-indexed table, since all
-- records have an ID.
--
-- It also keeps track of the next unique ID in next_id
--
-- It also has two methods:
--
-- get(uid) - takes an effect id and returns the effect record if it exists
-- effects() - iterator over the effect ids and effect records
--
-- Setmap
--
-- A set map is a map from indices to sets of records. They are used to represent
-- many-to-many and one-to-many relations.
-- Version of db format
local db_version = 0
local table_names = { "player", "tag", "monoid", "name", "perm" }
-- Returns a set of results
local function setmap_get(setmap, index)
return setmap[index]
end
local function setmap_insert(setmap, indices, uid, record)
for index, v in pairs(indices) do
local set = setmap[index]
if (not set) then
set = {}
setmap[index] = set
end
set[uid] = record
end
end
local function setmap_delete(setmap, indices, uid)
for index, v in pairs(indices) do
local set = setmap[index]
if set ~= nil then
set[uid] = nil
-- If the set is empty we should clean it up to prevent
-- leaks
if next(set) == nil then
setmap[index] = nil
end
end
end
end
-- Returns a new set that's the intersection of the two
local function set_intersect(set1,set2)
local res_set = {}
for k in pairs(set1) do
res_set[k] = set2[k]
end
return res_set
end
-- Returns a new map, using the function to resolve conflicts
local function map_intersect_with(f, set1, set2)
local res_map = {}
for k, v in pairs(set1) do
local v2 = set2[k]
if v2 ~= nil then
res_map[k] = f(v, v2)
end
end
return res_map
end
-- Returns a new setmap. Will be faster if you put the smaller one first.
local function setmap_intersect(smap1, smap2)
return map_intersect_with(set_intersect, smap1, smap2)
end
local function set_union(set1, set2)
local res_set = {}
for k, v in pairs(set1) do
res_set[k] = true
end
for k, v in pairs(set2) do
res_set[k] = true
end
return res_set
end
local function map_union_with(f, map1, map2)
local res_map = {}
for k, v in pairs(map1) do
res_map[k] = v
end
for k, v in pairs(map2) do
local existing = res_map[k]
if (existing) then
res_map[k] = f(existing, v)
else
res_map[k] = v
end
end
return res_map
end
-- Returns a new setmap. *I think* faster if the first is smaller.
local function setmap_union(smap1, smap2)
return map_union_with(set_union, smap1, smap2)
end
local function record(dyn, effect_type, players, tags, monoids, dur, values)
return { dynamic = dyn,
effect_type = effect_type,
players = players,
tags = tags,
monoids = monoids,
values = values,
duration = dur,
}
end
local function is_perm(record)
return record.duration == "perm"
end
local function shallow_copy(x)
local res = {}
for k, v in pairs(x) do
res[k] = v
end
return res
end
local function effect_set_get(eset, uid)
return eset.uid_table[uid]
end
local function effect_set_effects(eset)
return pairs(eset.uid_table)
end
-- Returns a set of UIDs that match the given index
local function effect_set_index_set(db, table_name, index)
return db.tables[table_name][index] or {}
end
local function fill_tables(db)
db.tables = {}
for i, table_name in ipairs(table_names) do
db.tables[table_name] = {}
end
for k, v in pairs(db.uid_table) do
insert_record_with_uid(k, db, v)
end
end
-- Mutates the DB to have the new record
local function insert_record_with_uid(uid, db, record)
db.uid_table[uid] = record
setmap_insert(db.tables.player, record.players, uid, record)
setmap_insert(db.tables.tag, record.tags, uid, record)
setmap_insert(db.tables.monoid, record.monoids, uid, record)
setmap_insert(db.tables.name, {[record.effect_type] = true}, uid, record)
local perm = is_perm(record)
setmap_insert(db.tables.perm, {[perm] = true}, uid, record)
end
local function insert_record(db, record)
local uid = db.next_id
db.next_id = uid + 1
insert_record_with_uid(uid, db, record)
return uid
end
-- Mutates the DB to remove the record. Returns the deleted record, or nil if
-- it was not found.
local function delete_record(db, uid)
local record = db.uid_table[uid]
if (record == nil) then return end
local players = record.players
local tags = record.tags
local monoids = record.monoids
local effect_type = record.effect_type
local perm = is_perm(record)
db.uid_table[uid] = nil
setmap_delete(db.tables.player, players, uid)
setmap_delete(db.tables.tag, tags, uid)
setmap_delete(db.tables.monoid, monoids, uid)
setmap_delete(db.tables.name, {[effect_type] = true}, uid)
setmap_delete(db.tables.perm, {[perm] = true}, uid)
end
local methods =
{
get = effect_set_get,
effects = effect_set_effects,
insert = insert_record,
delete = delete_record,
with_index = effect_set_index_set,
}
local function add_methods(eset)
for name, method in pairs(methods) do
eset[name] = method
end
end
local function del_methods(eset)
for name, method in pairs(methods) do
eset.name = nil
end
end
local function make_db()
local db = {}
db.version = db_version
db.next_id = 1
db.tables = {}
db.uid_table = {}
db.tables.player = {}
db.tables.tag = {}
db.tables.monoid = {}
db.tables.name = {}
db.tables.perm = {}
add_methods(db)
return db
end
local function serialize_effect_set(eset)
local serialize_this = shallow_copy(eset.uid_table)
serialize_this.next_id = eset.next_id
return minetest.serialize(serialize_this)
end
local function deserialize_effect_set(str)
local uid_table = minetest.deserialize(str)
local deserialized = {uid_table = uid_table, tables = {}}
local tables = {}
for i, table_name in ipairs(table_names) do
tables[table_name] = {}
end
deserialized.tables = tables
deserialized.next_id = uid_table.next_id
uid_table.next_id = nil
if (uid_table == nil) then
return nil
end
for k, v in pairs(uid_table) do
insert_record_with_uid(k, deserialized, v)
end
add_methods(deserialized)
return deserialized
end
-- Mini namespace for effect sets
local effectset = {}
effectset.new_set = make_db
effectset.serialize = serialize_effect_set
effectset.deserialize = deserialize_effect_set
effectset.intersect = function(es1, es2)
local es = {}
es.uid_table = set_intersect(es1.uid_table, es2.uid_table)
es.tables = map_intersect(setmap_intersect, es1.tables, es2.tables)
add_methods(es)
return es
end
effectset.union = function(es1, es2)
local es = {}
es.uid_table = set_union(es1.uid_table, es2.uid_table)
es.tables = map_intersect(setmap_union, es1.tables, es2.tables)
add_methods(es)
return es
end
effectset.set_intersect = set_intersect
effectset.set_union = set_union
effectset.record = record
return effectset