You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Assets/Engine/Scripts/Utils/Containers.lua

330 lines
9.0 KiB
Lua

----------------------------------------------------------------------------------------------------
--
-- Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
--
-- SPDX-License-Identifier: Apache-2.0 OR MIT
--
--
--
--
--
-- Description: Containers for Lua
-- Docs:
--
----------------------------------------------------------------------------------------------------
local countTable = Set and Set.countTable or {};
-- Create factory and management table
Set = {};
Set.countTable = countTable;
-- All tables are registered in Set.countTable, therefore won't GCed!
-- Unless we make their entries weak references. See Lua docs.
-- The effect is that these references are ignored by the garbage collector.
setmetatable(Set.countTable, { __mode="kv" });
-- Create and return a new Set table
Set.New = function()
local nt = {}
Set.countTable[nt] = 0
return nt
end
Set.SerializeValues = function(tbl)
local result = {}
for k,v in pairs(tbl) do
local item = {}
table.insert( item, 1, k );
table.insert( item, 2, v );
table.insert( result, item );
end
return result
end
Set.DeserializeValues = function(tbl)
local result = Set.New()
for i,item in ipairs(tbl) do
Set.Add( result, item[1], item[2] );
end
return result;
end
Set.DeserializeEntities = function(tbl)
local result = Set.New()
for i,entityID in ipairs(tbl) do
Set.Add(result, entityID)
end
return result
end
Set.SerializeEntities = function(tbl)
local result = {};
for entityID,v in pairs(tbl) do
table.insert(result, entityID)
end
return result;
end
Set.DeserializeItems = function(tbl)
local result = Set.New();
for i,item in ipairs(tbl) do
if (item) then
Set.Add(result, item);
end
end
return result;
end
Set.SerializeItems = function(tbl)
local result = {};
local index = 1;
for item,v in pairs(tbl) do
result[index] = item;
index = index + 1;
end
return result;
end
-- Is this table registered as a Set?
-- When performance and best-effort execution are required, this function
-- can easily be reduced to a stub or even all calls to it removed with regexp
Set.Check = function(table)
-- Run-time checking
if (not Set.countTable[table]) then
-- Throwing an error here would be preferable
if (print) then
print(tostring(table).."is not registered as a Set");
else
System.Log(tostring(table).."is not registered as a Set");
end
System.ShowDebugger()
Set.throwAnError.throwAnError = true;
end
end
-- Add an entry into a Set iff it does not already exist
-- if v == nil, we set it to true - leaving it nil would break things
-- Returns true on success, false if entry already exists
Set.Add = function(table, k, v)
v = v or true;
Set.Check(table);
if (not table[k]) then
table[k] = v;
Set.countTable[table] = Set.countTable[table] + 1;
return true;
end
return false;
end
-- Removes an entry from a Set
-- if k == nil, does nothing, which is debatable semantics
-- Returns true on success, false if entry did not exist
Set.Remove = function(table, k)
Set.Check(table);
if (table[k]) then
table[k] = nil;
Set.countTable[table] = Set.countTable[table] - 1;
return true;
end
return false;
end
-- Get a value from a Set
-- Return the value or nil if none
Set.Get = function(table, k)
Set.Check(table);
return table[k]
end
-- Set the value associated with a key, even if it already exists
-- if v == nil, we set it to true - leaving it nil would break things
-- Returns true if it did already exist, false if not
Set.Set = function(table, k, v)
v = v or true;
Set.Check(table);
if (not table[k]) then
table[k] = v;
Set.countTable[table] = Set.countTable[table] + 1;
return false;
else
-- Replace, no need to change count
table[k] = v;
return true;
end
end
-- Get the number of entries in the Set
Set.Size = function(table)
Set.Check(table);
return Set.countTable[table];
end
-- Remove all entries
-- No return value
Set.RemoveAll = function(table)
Set.Check(table);
for k,v in pairs(table) do
table[k] = nil;
end
Set.countTable[table] = 0;
end
-- Add entries of one Set into another
-- Keys shared by both will not be overwritten
-- Returns true iff keys were disjoint, none shared
Set.Merge = function(dest, source)
local disjoint = true;
Set.Check(dest);
Set.Check(source);
for k,v in pairs(source) do
if (not Set.Add(dest, k, v)) then
disjoint = false;
end
end
return disjoint;
end
-- Iterating:
-- For now, treat a Set as any table.
-- This should change.
-- Utility/debugging function:
-- Sanity check a suspicious Set
-- Tries to find evidence of tampering
-- Returns true iff it passes
Set.SanityCheck = function(table)
-- Check this was created as a Set
Set.Check(table);
-- Find as many things as you can in the Set
local count = 0;
for i,v in pairs(table) do
count = count + 1;
end
local size = Set.Size(table);
if (count ~= size) then
System.Log("[Set] Sanity check failed - Set size is "..tostring(size)..", counted "..count);
return false;
end
-- Check that all entries have the same type
-- This is just good practice, rather than an error
local indexType = nil;
local valueType = nil;
for i,v in pairs(table) do
-- Check index
if (indexType) then
if (indexType ~= type(i)) then
System.Log("[Set] Sanity check failed - Found indices of both types "..indexType.." and "..type(i));
return false;
end
else
indexType = type(i);
end
-- Check value
if (valueType) then
if (valueType ~= type(v)) then
System.Log("[Set] Sanity check failed - Found values of both types "..valueType.." and "..type(v));
return false;
end
else
valueType = type(v);
end
end
return true;
end
-- Unit test
-- Return true for success or false on failure
Set.Test = function(fullTest)
local A = Set.New();
local B = Set.New();
if ( Set.Add(A, "key1", 1) == false ) then return false; end
if ( Set.Add(A, "key2") == false ) then return false; end
if ( Set.Add(A, "key3", 3) == false ) then return false; end
if ( Set.Add(A, "key1", 1) == true ) then return false; end
if ( Set.Remove(A, "key1") == false ) then return false; end
if ( Set.Get(A, "key2")== false ) then return false; end
if ( Set.Get(A, "key1") ~= nil ) then return false; end
if ( Set.Get(A, "key3") ~= 3 ) then return false; end
if ( Set.Size(A) ~= 2 ) then return false; end
Set.RemoveAll(A);
if ( Set.Size(A) ~= 0 ) then return false; end
if ( Set.Add(A, "key1", 1) == false ) then return false; end
if ( Set.Set(A, "key1", 9) == false ) then return false; end
if ( Set.Set(A, "key0", 9) == true ) then return false; end
if ( Set.Add(A, "keyF", 3) == false ) then return false; end
if ( Set.Size(A) ~= 3 ) then return false; end
if ( Set.Add(B, "key3", 3) == false ) then return false; end
if ( Set.Add(B, "key4", 4) == false ) then return false; end
if ( Set.Add(B, "key5", 5) == false ) then return false; end
if ( Set.Add(B, "key3", 3) == true ) then return false; end
if ( Set.Set(B, "key9") == true ) then return false; end
if ( Set.Get(B, "key9") == nil ) then return false; end
if (not Set.Merge(A,B)) then return false; end
if (Set.Size(A) ~= 7) then return false; end
if (Set.Merge(A,B)) then return false; end
if (Set.Size(A) ~= 7) then return false; end
if (fullTest) then
-- Check countTable and GC (slow)
-- Shouldn't assume there are no Sets when testing
collectgarbage();
local baseCount = 0;
for i,v in pairs(Set.countTable) do
baseCount = baseCount + 1;
end
B = nil;
collectgarbage();
local count = 0;
for i,v in pairs(Set.countTable) do
count = count + 1;
end
if (baseCount - count ~= 1) then return false; end
end
-- Move this line around for testing
do return true; end
return true;
end
-- Perform quick version of unit test
if (not Set.Test()) then
System.Log("Containers: ... Error - Failed Unit Test");
else
--System.Log("Containers: ... End loading!");
end
-- Useful testing lines in Editor:
-- #Script.ReloadScript("Scripts/Utils/Containers.lua");
-- #System.Log(tostring(Set.Test(true)))