text: --- Lua-side storage for safe references to C++ objects.
  --
  -- Whenever a new character or item is initialized, a corresponding
  -- handle will be created here to track the object's lifetime in an
  -- isolated Lua environment managed by a C++ handle manager. If the
  -- object is no longer valid for use (a character died, or an item was
  -- destroyed), the C++ side will set the Lua side's handle to be
  -- invalid. An error will be thrown on trying to access or write to
  -- anything on an invalid handle. Since objects are identified by
  -- UUIDs, it is possible to serialize references to C++ objects
  -- relatively easily, allowing for serializing the state of any mods
  -- that are in use along with the base save data. The usage of UUIDs
  -- also allows checking equality and validity of objects, even long
  -- after the C++ object the handle references has been removed.
  --
  -- Borrowed from https://eliasdaler.github.io/game-object-references/
  
  local Handle = {}
  
  -- Stores a map of handle.__uuid -> C++ object reference. These should not be
  -- directly accessable outside this chunk.
  -- Indexed by [class_name][uuid].
  local refs = {}
  
  -- Stores a map of raw pointer -> handle.
  -- Indexed by [class_name][id].
  local handles_by_pointer = {}
  
  -- Cache for function closures resolved when indexing a handle.
  -- Creating a new closure for every method lookup is expensive.
  -- Indexed by [class_name][method_name].
  local memoized_funcs = {}
  
  
  local function handle_error(handle, key)
     if _IS_TEST then
        return
     end
  
     if ELONA and ELONA.require then
        local GUI = ELONA.require("core.GUI")
        GUI.txt_color(3)
        GUI.txt("Error: handle is not valid! ")
        if key ~= nil then
           GUI.txt("Indexing: " .. tostring(key) .. " ")
        end
        GUI.txt("This means the character/item got removed. ")
        GUI.txt_color(0)
     end
  
     if handle then
        print("Error: handle is not valid! " .. handle.__kind .. ":" .. handle.__uuid)
     else
        print("Error: handle is not valid! " .. tostring(handle))
     end
     print(debug.traceback())
  
     error("Error: handle is not valid!", 2)
  end
  
  
  function Handle.is_valid(handle)
     return handle ~= nil and refs[handle.__kind][handle.__uuid] ~= nil
  end
  
  
  --- Create a metatable to be set on all handle tables of a given kind
  --- ("LuaCharacter", "LuaItem") which will check for validity on
  --- variable/method access.
  local function generate_metatable(kind)
     -- The userdata table is bound by sol2 as a global.
     local userdata_table = _ENV[kind]
  
     local mt = {}
     memoized_funcs[kind] = {}
     mt.__index = function(handle, key)
        if key == "is_valid" then
           -- workaround to avoid serializing is_valid function, since
           -- serpent will refuse to load it safely
           return Handle.is_valid
        end
  
        if not Handle.is_valid(handle)then
           handle_error(handle, key)
        end
  
        -- Try to get a property out of the C++ reference.
        local val = refs[kind][handle.__uuid][key]
  
        if val ~= nil then
           -- If the found property is a plain value, return it.
           if type(val) ~= "function" then
              return val
           end
        end
  
        -- If that fails, try calling a function by the name given.
        local f = memoized_funcs[kind][key]
        if not f then
           -- Look up the function on the usertype table generated by
           -- sol2.
           f = function(h, ...)
              if type(h) ~= "table" or not h.__handle then
                 error("Please call this function using colon syntax (':').")
              end
              return userdata_table[key](refs[kind][h.__uuid], ...)
           end
  
           -- Cache it so we don't incur the overhead of creating a
           -- closure on every lookup.
           memoized_funcs[kind][key] = f
        end
        return f
     end
     mt.__newindex = function(handle, key, value)
        if not Handle.is_valid(handle) then
           handle_error(handle, key)
        end
  
        refs[kind][handle.__uuid][key] = value
     end
     mt.__eq = function(lhs, rhs)
        return lhs.__kind == rhs.__kind and lhs.__uuid == rhs.__uuid
     end
     mt.__tostring = function(handle)
        local ref = refs[handle.__kind][handle.__uuid]
        if ref then
           return userdata_table.__tostring(ref)
        else
           return "nil"
        end
     end
     -- for serpent
     mt.__serialize = function(handle)
        return handle
     end
  
     refs[kind] = {}
     handles_by_pointer[kind] = {}
  
     return mt
  end
  
  local metatables = {}
  metatables.LuaCharacter = generate_metatable("LuaCharacter")
  metatables.LuaItem = generate_metatable("LuaItem")
  
  --- Given a valid handle and kind, retrieves the underlying C++
  --- userdata reference.
  function Handle.get_ref(handle, kind)
     if not Handle.is_valid(handle) then
        handle_error(handle)
        return nil
     end
  
     if not handle.__kind == kind then
        print(debug.traceback())
        error("Error: handle is of wrong type: wanted " .. kind .. ", got " .. handle.__kind)
        return nil
     end
  
     return refs[kind][handle.__uuid]
  end
  
  function Handle.set_ref(handle, ref)
     refs[handle.__kind][handle.__uuid] = ref
  end
  
  --- Gets a metatable for the lua type specified ("LuaItem",
  --- "LuaCharacter")
  function Handle.get_metatable(kind)
     return metatables[kind]
  end
  
  --- Given a raw pointer of a C++ object and kind, retrieves the
  --- handle that references it.
  function Handle.get_handle(pointer, kind)
     local handle = handles_by_pointer[kind][pointer]
  
     if handle and handle.__kind ~= kind then
        print(debug.traceback())
        error("Error: handle is of wrong type: wanted " .. kind .. ", got " .. handle.__kind)
        return nil
     end
  
     return handle
  end
  
  --- Creates a new handle by using a C++ raw pointer. The handle's pointer must
  --- not be occupied by another handle, to prevent overwrites.
  function Handle.create_handle(cpp_ref, pointer, kind, uuid)
     if handles_by_pointer[kind][pointer] ~= nil then
        print(handles_by_pointer[kind][pointer].__uuid)
        error("Handle already exists: " .. kind .. ":" .. pointer, 2)
        return nil
     end
  
     -- print("CREATE " .. kind .. " " .. pointer .. " " .. uuid)
  
     local handle = {
        __uuid = uuid,
        __kind = kind,
        __handle = true
     }
  
     setmetatable(handle, metatables[handle.__kind])
     refs[handle.__kind][handle.__uuid] = cpp_ref
     handles_by_pointer[handle.__kind][pointer] = handle
  
     return handle
  end
  
  
  --- Removes an existing handle by using a C++ raw pointer.
  --- It is acceptable if the handle doesn't already exist.
  function Handle.remove_handle(cpp_ref, pointer, kind)
     local handle = handles_by_pointer[kind][pointer]
  
     if handle == nil then
        return
     end
  
     -- print("REMOVE " .. pointer .. " " .. handle.__uuid)
  
     assert(handle.__kind == kind)
  
     refs[handle.__kind][handle.__uuid] = nil
     handles_by_pointer[handle.__kind][pointer] = nil
  end
  
  
  --- Moves a handle from one raw pointer to another if it exists. If the handle
  --- exists, the target slot must not be occupied. If not, the destination slot
  --- will be set to empty as well.
  function Handle.relocate_handle(cpp_ref, pointer, dest_cpp_ref, new_pointer, kind)
     local handle = handles_by_pointer[kind][pointer]
  
     if Handle.is_valid(handle) then
        handles_by_pointer[kind][new_pointer] = handle
        Handle.set_ref(handle, dest_cpp_ref)
     else
        -- When the handle is not valid, set the destination slot to be
        -- invalid as well, to reflect relocating an empty handle.
  
        -- This can happen when a temporary character is created as part
        -- of change creature magic, as the temporary's state will be
        -- empty at the time of relocation.
        handles_by_pointer[kind][pointer] = nil
     end
  
     -- Clear the slot the handle was moved from.
     handles_by_pointer[kind][pointer] = nil
  end
  
  --- Exchanges the positions of two handles and updates their __index
  --- fields with the new values. Both handles must be valid.
  function Handle.swap_handles(cpp_ref_a, cpp_ref_b, kind)
     -- Do nothing.
  end
  
  
  -- Functions for deserialization. The steps are as follows.
  -- 1. Deserialize mod data that contains the table of handles.
  -- 2. Load handles onto the "handles_by_pointer" table using
  --    "merge_handles".
  -- 3. In C++, for each object loaded, add its reference to the "refs"
  --    table using by looking up a newly inserted handle using the C++
  --    raw pointer in "handles_by_pointer".
  --    See also HandleManager::resolve_handle in C++.
  
  function Handle.merge_handles(kind, obj_ids)
     for pointer, obj_id in pairs(obj_ids) do
        if obj_id ~= nil then
           local handle = {
              __uuid = obj_id,
              __kind = kind,
              __handle = true,
           }
           setmetatable(handle, metatables[kind])
           handles_by_pointer[kind][pointer] = handle
        end
     end
  end
  
  
  function Handle.clear()
     for kind, _ in pairs(refs) do
        refs[kind] = {}
     end
  
     for kind, _ in pairs(handles_by_pointer) do
        handles_by_pointer[kind] = {}
     end
  end
  
  
  
  return Handle
  