diff --git a/ModUtil.Compat.lua b/ModUtil.Compat.lua new file mode 100644 index 0000000..60ceb5b --- /dev/null +++ b/ModUtil.Compat.lua @@ -0,0 +1,120 @@ + +ModUtil.Mod.Register( "Compat", ModUtil ) + +setmetatable( ModUtil, { + __index = ModUtil.Compat +} ) + +ModUtil.Compat.RegisterMod = ModUtil.Mod.Register + +ModUtil.Compat.ForceClosed = ModUtil.Internal.ForceClosed + +ModUtil.Compat.ValueString = ModUtil.ToString.Value + +ModUtil.Compat.KeyString = ModUtil.ToString.Key + +ModUtil.Compat.TableKeysString = ModUtil.ToString.TableKeys + +ModUtil.Compat.ToShallowString = ModUtil.ToString.Shallow + +ModUtil.Compat.ToDeepString = ModUtil.ToString.Deep + +ModUtil.Compat.ToDeepNoNamespacesString = ModUtil.ToString.Deep.NoNamespaces + +ModUtil.Compat.ToDeepNamespacesString = ModUtil.ToString.Deep.Namespaces + +ModUtil.Compat.JoinStrings = ModUtil.String.Join + +ModUtil.Compat.ChunkText = ModUtil.String.Chunk + +ModUtil.Compat.ReplaceTable = ModUtil.Table.Replace + +ModUtil.Compat.IsUnKeyed = ModUtil.Table.UnKeyed + +ModUtil.Compat.PrintToFile = ModUtil.Print.ToFile + +ModUtil.Compat.DebugPrint = ModUtil.Print.Debug + +ModUtil.Compat.PrintTraceback = ModUtil.Print.Traceback + +ModUtil.Compat.PrintNamespaces = ModUtil.Print.Namespaces + +ModUtil.Compat.PrintVariables = ModUtil.Print.Variables + +ModUtil.Compat.Slice = ModUtil.Array.Slice + +ModUtil.Compat.NewTable = ModUtil.Node.New + +ModUtil.Compat.SafeGet = ModUtil.IndexArray.Get + +ModUtil.Compat.SafeSet = ModUtil.IndexArray.Set + +ModUtil.Compat.MapNilTable = ModUtil.Table.NilMerge + +ModUtil.Compat.MapSetTable = ModUtil.Table.Merge + +ModUtil.Compat.JoinIndexArrays = ModUtil.Array.Join + +ModUtil.Compat.PathToIndexArray = ModUtil.Path.IndexArray + +ModUtil.Compat.PathGet = ModUtil.Path.Get + +ModUtil.Compat.PathSet = ModUtil.Path.Set + +ModUtil.Compat.WrapFunction = ModUtil.IndexArray.Wrap + +ModUtil.Compat.RewrapFunction = ModUtil.IndexArray.Decorate.Refresh + +ModUtil.Compat.UnwrapFunction = ModUtil.IndexArray.Decorate.Pop + +ModUtil.Compat.WrapBaseFunction = ModUtil.Path.Wrap + +ModUtil.Compat.RewrapBaseFunction = ModUtil.Path.Decorate.Refresh + +ModUtil.Compat.UnwrapBaseFunction = ModUtil.Path.Decorate.Pop + +ModUtil.Compat.Override = ModUtil.IndexArray.Override + +ModUtil.Compat.BaseOverride = ModUtil.Path.Override + +ModUtil.Compat.GetOriginalValue = ModUtil.IndexArray.Original + +ModUtil.Compat.GetOriginalBaseValue = ModUtil.Path.Original + +ModUtil.Compat.RawInterface = ModUtil.Raw + +ModUtil.Compat.MapVars = ModUtil.Args.Map + +ModUtil.Compat.StackedUpValues = ModUtil.UpValues.Stacked + +ModUtil.Compat.StackedLocals = ModUtil.Locals.Stacked + +ModUtil.Compat.LocalValues = ModUtil.Locals.Values + +ModUtil.Compat.LocalNames = ModUtil.Locals.Names + +function ModUtil.Compat.GetBaseBottomUpValues( funcPath ) + return ModUtil.UpValues( ModUtil.Path.Original( funcPath ) ) +end + +function ModUtil.Compat.MapTable( mapFunc, tableArg ) + return ModUtil.Table.Map( tableArg, mapFunc ) +end + +function ModUtil.Compat.WrapWithinFunction( baseTable, indexArray, envIndexArray, wrapFunc, mod ) + ModUtil.IndexArray.Context.Wrap( baseTable, indexArray, function( ) + ModUtil.IndexArray.Wrap( _G, envIndexArray, wrapFunc, mod ) + end ) +end + +function ModUtil.Compat.WrapBaseWithinFunction( funcPath, baseFuncPath, wrapFunc, mod ) + ModUtil.Path.Context.Wrap( baseFuncPath, function( ) + ModUtil.Path.Wrap( funcPath, wrapFunc, mod ) + end ) +end + +function ModUtil.BaseOverrideWithinFunction( funcPath, basePath, value, mod ) + ModUtil.Path.Context.Wrap( funcPath, function( ) + ModUtil.Path.Override( basePath, value, mod ) + end ) +end diff --git a/ModUtil.Extra.lua b/ModUtil.Extra.lua new file mode 100644 index 0000000..8837fae --- /dev/null +++ b/ModUtil.Extra.lua @@ -0,0 +1,111 @@ +--- + +ModUtil.IndexArray.Context = { } + +function ModUtil.IndexArray.Wrap( baseTable, indexArray, wrap, mod ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Wrap, wrap, mod ) +end + +ModUtil.IndexArray.Context.Wrap = ModUtil.Callable.Set( { }, function( _, baseTable, indexArray, context, mod ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Context.Wrap, context, mod ) +end ) + +function ModUtil.IndexArray.Context.Wrap.Static( baseTable, indexArray, context, mod ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Context.Wrap.Static, context, mod ) +end + +ModUtil.IndexArray.Decorate = ModUtil.Callable.Set( { }, function( _, baseTable, indexArray, func, mod ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Decorate, func, mod ) +end ) + +function ModUtil.IndexArray.Decorate.Pop( baseTable, indexArray ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Decorate.Pop ) +end + +function ModUtil.IndexArray.Decorate.Refresh( baseTable, indexArray ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Decorate.Refresh ) +end + +function ModUtil.IndexArray.Override( baseTable, indexArray, value, mod ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Override, value, mod ) +end + +function ModUtil.IndexArray.Restore( baseTable, indexArray ) + return ModUtil.IndexArray.Map( baseTable, indexArray, ModUtil.Restore ) +end + +function ModUtil.IndexArray.Overriden( baseTable, indexArray ) + return ModUtil.Overriden( ModUtil.IndexArray.Get( baseTable, indexArray ) ) +end + +function ModUtil.IndexArray.Original( baseTable, indexArray ) + return ModUtil.Original( ModUtil.IndexArray.Get( baseTable, indexArray ) ) +end + +function ModUtil.IndexArray.ReferFunction( baseTable, indexArray ) + return ModUtil.ReferFunction( function( ... ) + ModUtil.Get( ModUtil.IndexArray.Get( ... ) ) + end, baseTable, indexArray ) +end + +function ModUtil.IndexArray.ReferTable( baseTable, indexArray ) + return ModUtil.ReferTable( function( ... ) + ModUtil.Get( ModUtil.IndexArray.Get( ... ) ) + end, baseTable, indexArray ) +end + +--- + +ModUtil.Path.Context = { } + +function ModUtil.Path.Wrap( path, wrap, mod ) + return ModUtil.Path.Map( path, ModUtil.Wrap, wrap, mod ) +end + +ModUtil.Path.Context.Wrap = ModUtil.Callable.Set( { }, function( _, path, context, mod ) + return ModUtil.Path.Map( path, ModUtil.Context.Wrap, context, mod ) +end ) + +function ModUtil.Path.Context.Wrap.Static( path, context, mod ) + return ModUtil.Path.Map( path, ModUtil.Context.Wrap.Static, context, mod ) +end + +ModUtil.Path.Decorate = ModUtil.Callable.Set( { }, function( _, path, func, mod ) + return ModUtil.Path.Map( path, ModUtil.Decorate, func, mod ) +end ) + +function ModUtil.Path.Decorate.Pop( path ) + return ModUtil.Path.Map( path, ModUtil.Decorate.Pop ) +end + +function ModUtil.Path.Decorate.Refresh( path ) + return ModUtil.Path.Map( path, ModUtil.Decorate.Refresh ) +end + +function ModUtil.Path.Override( path, value, mod ) + return ModUtil.Path.Map( path, ModUtil.Override, value, mod ) +end + +function ModUtil.Path.Restore( path ) + return ModUtil.Path.Map( path, ModUtil.Restore ) +end + +function ModUtil.Path.Overriden( path ) + return ModUtil.Overriden( ModUtil.Path.Get( path ) ) +end + +function ModUtil.Path.Original( path ) + return ModUtil.Original( ModUtil.Path.Get( path ) ) +end + +function ModUtil.Path.ReferFunction( path ) + return ModUtil.ReferFunction( function( ... ) + ModUtil.Path.Get( ... ) + end, path ) +end + +function ModUtil.Path.ReferTable( path ) + return ModUtil.ReferTable( function( ... ) + ModUtil.Path.Get( ... ) + end, path ) +end \ No newline at end of file diff --git a/ModUtilHades.lua b/ModUtil.Hades.lua similarity index 88% rename from ModUtilHades.lua rename to ModUtil.Hades.lua index 26b30a9..c4540ca 100644 --- a/ModUtilHades.lua +++ b/ModUtil.Hades.lua @@ -1,31 +1,57 @@ -ModUtil.RegisterMod( "Hades", ModUtil ) +ModUtil.Mod.Register( "Hades", ModUtil ) -ModUtil.MapSetTable( ModUtil.Hades, { +-- Global Interception + +--[[ + Intercept global keys which are objects to return themselves + This way we can use other namespaces for UI etc +--]] + +local callableCandidateTypes = ModUtil.Internal.callableCandidateTypes + +local function isPath( path ) + return path:find("[.]") + and not path:find("[.][.]+") + and not path:find("^[.]") + and not path:find("[.]$") +end + +local function routeKey( self, key ) + local t = type( key ) + if t == "string" and isPath( key ) then + return ModUtil.Path.Get( key ) + end + if callableCandidateTypes[ t ] then + return key + end +end + +do + + local meta = getmetatable( _ENV ) or { } + if meta.__index then + meta.__index = ModUtil.Wrap( meta.__index, function( base, self, key ) + local value = base( self, key ) + if value ~= nil then return value end + return routeKey( self, key ) + end, ModUtil.Hades ) + else + meta.__index = routeKey + end + + setmetatable( _ENV, meta ) + +end +--- + +ModUtil.Table.Merge( ModUtil.Hades, { PrintStackHeight = 10, PrintStackCapacity = 80 } ) ModUtil.Anchors.PrintOverhead = {} --- Screen Handling - -OnAnyLoad{ function() - if ModUtil.Hades.UnfreezeLoop then return end - ModUtil.Hades.UnfreezeLoop = true - thread( function() - while ModUtil.Hades.UnfreezeLoop do - wait(15) - if ModUtil.SafeGet(CurrentRun,{'Hero','FreezeInputKeys'}) then - if (not AreScreensActive()) and (not IsInputAllowed({})) then - UnfreezePlayerUnit() - DisableShopGamepadCursor() - end - end - end - end) -end} - -- Menu Handling function ModUtil.Hades.CloseMenu( screen, button ) @@ -305,7 +331,7 @@ end function ModUtil.Hades.PrintStackChunks( text, linespan, ... ) if not linespan then linespan = 90 end - for _,s in ipairs( ModUtil.ChunkText( text, linespan,ModUtil.Hades.PrintStackCapacity ) ) do + for _,s in ipairs( ModUtil.String.Chunk( text, linespan, ModUtil.Hades.PrintStackCapacity ) ) do ModUtil.Hades.PrintStack( s, ... ) end end @@ -349,13 +375,13 @@ function ModUtil.Hades.NewMenuYesNo( group, closeFunc, openFunc, yesFunc, noFunc Attach({ Id = components.Icon.Id, DestinationId = components.Background.Id, OffsetX = 0, OffsetY = -50}) SetAnimation({ Name = icon, DestinationId = components.Icon.Id, Scale = iconScale }) - ModUtil.NewTable(ModUtil.Anchors.Menu[group], "Funcs") + ModUtil.Nodes.New(ModUtil.Anchors.Menu[group], "Funcs") ModUtil.Anchors.Menu[group].Funcs={ Yes = function(screen, button) if not yesFunc(screen,button) then ModUtil.Hades.CloseMenuYesNo(screen,button) end - end, + end, No = function(screen, button) if not noFunc(screen,button) then ModUtil.Hades.CloseMenuYesNo(screen,button) @@ -365,11 +391,11 @@ function ModUtil.Hades.NewMenuYesNo( group, closeFunc, openFunc, yesFunc, noFunc components.CloseButton = CreateScreenComponent({ Name = "ButtonClose", Scale = 0.7, Group = group }) Attach({ Id = components.CloseButton.Id, DestinationId = components.Background.Id, OffsetX = 0, OffsetY = ScreenCenterY - 315 }) - components.CloseButton.OnPressedFunctionName = "ModUtil.Hades.CloseMenuYesNo" + components.CloseButton.OnPressedFunctionName = ModUtil.Path.ReferFunction( "ModUtil.Hades.CloseMenuYesNo" ) components.CloseButton.ControlHotkey = "Cancel" components.YesButton = CreateScreenComponent({ Name = "BoonSlot1", Group = group, Scale = 0.35, }) - components.YesButton.OnPressedFunctionName = "ModUtil.Anchors.Menu."..group..".Funcs.Yes" + components.YesButton.OnPressedFunctionName = ModUtil.Path.ReferFunction( "ModUtil.Anchors.Menu."..group..".Funcs.Yes" ) SetScaleX({Id = components.YesButton.Id, Fraction = 0.75}) SetScaleY({Id = components.YesButton.Id, Fraction = 1.15}) Attach({ Id = components.YesButton.Id, DestinationId = components.Background.Id, OffsetX = -150, OffsetY = 75 }) @@ -379,7 +405,7 @@ function ModUtil.Hades.NewMenuYesNo( group, closeFunc, openFunc, yesFunc, noFunc }) components.NoButton = CreateScreenComponent({ Name = "BoonSlot1", Group = group, Scale = 0.35, }) - components.NoButton.OnPressedFunctionName = "ModUtil.Anchors.Menu."..group..".Funcs.No" + components.NoButton.OnPressedFunctionName = ModUtil.Path.ReferFunction( "ModUtil.Anchors.Menu."..group..".Funcs.No" ) SetScaleX({Id = components.NoButton.Id, Fraction = 0.75}) SetScaleY({Id = components.NoButton.Id, Fraction = 1.15}) Attach({ Id = components.NoButton.Id, DestinationId = components.Background.Id, OffsetX = 150, OffsetY = 75 }) @@ -401,4 +427,13 @@ end function ModUtil.Hades.RandomElement( tableArg, rng ) local Collapsed = CollapseTable( tableArg ) return Collapsed[RandomInt( 1, #Collapsed, rng )] +end + +-- Internal Access + +do + local ups = ModUtil.UpValues( function( ) + return callableCandidateTypes, isPath, routeKey + end ) + ModUtil.Entangled.Union.Add( ModUtil.Internal, ups ) end \ No newline at end of file diff --git a/ModUtil.Main.lua b/ModUtil.Main.lua new file mode 100644 index 0000000..dabe274 --- /dev/null +++ b/ModUtil.Main.lua @@ -0,0 +1,234 @@ +--[[ + ModUtil Main + Components of ModUtil that depend on loading after Main.lua +]] + +ModUtil.Anchors = { + Menu = { }, + CloseFuncs = { } +} + +--- bind to locals to minimise environment recursion and improve speed +local ModUtil, pairs, ipairs, table, SaveIgnores, _G + = ModUtil, pairs, ipairs, table, SaveIgnores, ModUtil.Internal._G + +-- Management + +SaveIgnores[ "ModUtil" ] = true + +rawset( _ENV, "_DEBUG_G", _G ) +SaveIgnores[ "_DEBUG_G" ] = true + +--[[ + Create a namespace that can be used for the mod's functions + and data, and ensure that it doesn't end up in save files. + + modName - the name of the mod + parent - the parent mod, or nil if this mod stands alone +--]] +function ModUtil.Mod.Register( first, second ) + local modName, parent + if type( first ) == "string" then + modName, parent = first, second + else + modName, parent = second, first + end + if not parent then + parent = _G + SaveIgnores[ modName ] = true + end + local mod = { } + parent[ modName ] = mod + local path = ModUtil.Mods.Inverse[ parent ] + if path ~= nil then + path = path .. '.' + else + path = '' + end + path = path .. modName + ModUtil.Mods.Data[ path ] = mod + ModUtil.Identifiers.Inverse[ path ] = mod + return setmetatable( mod, { __index = ModUtil.Mod } ) +end + +local objectData = ModUtil.Internal.objectData +local passByValueTypes = ModUtil.Internal.passByValueTypes + +local function modDataProxy( value, level ) + level = ( level or 1 ) + 1 + local t = type( value ) + if passByValueTypes[ t ] or t == "string" then + return value + end + if t == "table" then + if getmetatable( value ) then + error( "saved data tables cannot have values with metatables", level ) + end + return ModUtil.Entangled.ModData( value, level ) + end + error( "saved data tables cannot have values of type "..t..".", level ) +end + +local function modDataKey( key, level ) + local t = type( key ) + if passByValueTypes[ t ] or t == "string" then + return key + end + error( "saved data tables cannot have keys of type "..t..".", ( level or 1 ) + 1 ) +end + +local function modDataPlain( obj, key, value, level ) + level = ( level or 1 ) + 1 + if modDataKey( key, level ) ~= nil then + local t = type( value ) + if passByValueTypes[ t ] or t == "string" then + obj[ key ] = value + elseif t == "table" then + if getmetatable( value ) then + local state, value = pcall( function( ) return objectData[ value ] end ) + if not state then + error( "saved data tables cannot have values with metatables", level ) + end + end + for k, v in pairs( value ) do + modDataPlain( value, k, v, level ) + end + obj[ key ] = value + else + error( "saved data tables cannot have values of type "..t..".", level ) + end + end +end + +ModUtil.Metatables.Entangled.ModData = { + __index = function( self, key ) + return modDataProxy( objectData[ self ][ key ], 2 ) + end, + __newindex = function( self, key, value ) + modDataPlain( objectData[ self ], key, value, 2 ) + end, + __len = function( self ) + return #objectData[ self ] + end, + __next = function( self, key ) + local key = next( objectData[ self ], key ) + if modDataKey( key, 2 ) ~= nil then + return key, modDataProxy( objectData[ self ][ key ], 2 ) + end + end, + __inext = function( self, idx ) + local idx = inext( objectData[ self ], idx ) + if modDataKey( idx, 2 ) ~= nil then + return idx, modDataProxy( objectData[ self ][ idx ], 2 ) + end + end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return qrawipairs( self ) + end +} + +function ModUtil.Entangled.ModData( value ) + return ModUtil.Proxy( value, ModUtil.Metatables.Entangled.ModData ) +end + +ModUtil.Mod.Data = setmetatable( { }, { + __call = function( _, mod ) + ModData = ModData or { } + local key = ModUtil.Mods.Inverse[ mod ] + local data = ModData[ key ] + if not data then + data = { } + ModData[ key ] = data + end + return modDataProxy( data, 2 ) + end, + __index = function( _, key ) + ModData = ModData or { } + return modDataProxy( ModData[ key ], 2 ) + end, + __newindex = function( _, key, value ) + ModData = ModData or { } + modDataPlain( ModData, key, value, 2 ) + end, + __len = function( ) + ModData = ModData or { } + return #ModData + end, + __next = function( _, key ) + ModData = ModData or { } + local key = next( ModData, key ) + if modDataKey( key, 2 ) ~= nil then + return key, modDataProxy( ModData[ key ], 2 ) + end + end, + __inext = function( _, idx ) + ModData = ModData or { } + local idx = inext( ModData, idx ) + if modDataKey( idx, 2 ) ~= nil then + return idx, modDataProxy( ModData[ idx ], 2 ) + end + end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return qrawipairs( self ) + end +} ) + +--[[ + Tell each screen anchor that they have been forced closed by the game +--]] +local function forceClosed( triggerArgs ) + for _, v in pairs( ModUtil.Anchors.CloseFuncs ) do + v( nil, nil, triggerArgs ) + end + ModUtil.Anchors.CloseFuncs = { } + ModUtil.Anchors.Menu = { } +end +OnAnyLoad{ function( triggerArgs ) forceClosed( triggerArgs ) end } + +local funcsToLoad = { } + +local function loadFuncs( triggerArgs ) + for _, v in pairs( funcsToLoad ) do + v( triggerArgs ) + end + funcsToLoad = { } +end +OnAnyLoad{ function( triggerArgs ) loadFuncs( triggerArgs ) end } + +--[[ + Run the provided function once on the next in-game load. + + triggerFunction - the function to run +--]] +function ModUtil.LoadOnce( triggerFunction ) + table.insert( funcsToLoad, triggerFunction ) +end + +--[[ + Cancel running the provided function once on the next in-game load. + + triggerFunction - the function to cancel running +--]] +function ModUtil.CancelLoadOnce( triggerFunction ) + for i, v in ipairs( funcsToLoad ) do + if v == triggerFunction then + table.remove( funcsToLoad, i ) + end + end +end + +-- Internal Access + +do + local ups = ModUtil.UpValues( function( ) + return _G, forceClosed, funcsToLoad, loadFuncs, + objectData, passByValueTypes, modDataKey, modDataProxy, modDataPlain + end ) + ModUtil.Entangled.Union.Add( ModUtil.Internal, ups ) +end \ No newline at end of file diff --git a/ModUtil.lua b/ModUtil.lua index fabb149..e4e5782 100644 --- a/ModUtil.lua +++ b/ModUtil.lua @@ -2,35 +2,47 @@ Mod: Mod Utility Author: MagicGonads - Library to allow mods to be more compatible with eachother and expand capabilities. - Use the mod importer to import this mod to ensure it is loaded in the right position. - -]] + Library to allow mods to be more compatible and expand capabilities. +--]] ModUtil = { - Internal = { }, - Metatables = { }, - New = { }, - Anchors = { - Menu = { }, - CloseFuncs = { } - }, + + Mod = { }, + Args = { }, + String = { }, + Table = { }, + Path = { }, + Array = { }, + IndexArray = { }, + Entangled = { }, + Metatables = { } + } -SaveIgnores[ "ModUtil" ] = true -- Extended Global Utilities (assuming lua 5.2) -local debug = debug +local error, pcall, xpcall = error, pcall, xpcall + +local debug, type, table = debug, type, table +local function getname( ) + return debug.getinfo( 2, "n" ).name +end -- doesn't invoke __index rawnext = next local rawnext = rawnext +local function pusherror( f, ... ) + local ret = table.pack( pcall( f, ... ) ) + if ret[ 1 ] then return table.unpack( ret, 2, ret.n ) end + error( ret[ 2 ], 3 ) +end + -- invokes __next function next( t, k ) local m = debug.getmetatable( t ) local f = m and m.__next or rawnext - return f( t, k ) + return pusherror( f, t, k ) end local next = next @@ -49,7 +61,19 @@ local rawget = rawget -- doesn't invoke __index just like rawnext function rawinext( t, i ) - if i == nil then i = 0 end + + if type( t ) ~= "table" then + error( "bad argument #1 to '" .. getname( ) .. "'(table expected got " .. type( i ) ..")", 2 ) + end + + if i == nil then + i = 0 + elseif type( i ) ~= "number" then + error( "bad argument #2 to '" .. getname( ) .. "'(number expected got " .. type( i ) ..")", 2 ) + elseif i < 0 then + error( "bad argument #2 to '" .. getname( ) .. "'(index out of bounds, too low)", 2 ) + end + i = i + 1 local v = rawget( t, i ) if v ~= nil then @@ -63,7 +87,7 @@ local rawinext = rawinext function inext( t, i ) local m = debug.getmetatable( t ) local f = m and m.__inext or rawinext - return f( t, i ) + return pusherror( f, t, i ) end local inext = inext @@ -82,8 +106,6 @@ function qrawipairs( t ) end, t, nil end -local type = type - function getfenv( fn ) if type( fn ) ~= "function" then fn = debug.getinfo( ( fn or 1 ) + 1, "f" ).func @@ -103,8 +125,8 @@ end Global variable lookups (including function calls) in that function will use the new environment table rather than the normal one. This is useful for function-specific overrides. The new environment - table should generally have _G as its __index, so that any globals - other than those being overridden can still be read. + table should generally have _G as its __index and __newindex, so that any globals + other than those being deliberately overridden operate as usual. ]] function setfenv( fn, env ) if type( fn ) ~= "function" then @@ -118,13 +140,11 @@ function setfenv( fn, env ) debug.upvaluejoin( fn, i, ( function( ) return env end ), 1 ) - return env + return end until not name end -local table = table - table.rawinsert = table.insert -- table.insert that respects metamethods function table.insert( list, pos, value ) @@ -134,7 +154,7 @@ function table.insert( list, pos, value ) pos = last + 1 end if pos < 1 or pos > last + 1 then - error( "bad argument #2 to '" .. debug.getinfo( 1, "n" ).name .. "' (position out of bounds)", 2 ) + error( "bad argument #2 to '" .. getname( ) .. "' (position out of bounds)", 2 ) end if pos <= last then local i = last @@ -154,7 +174,7 @@ function table.remove( list, pos ) pos = last end if pos < 1 or pos > last then - error( "bad argument #2 to '" .. debug.getinfo( 1, "n" ).name .. "' (position out of bounds)", 2 ) + error( "bad argument #2 to '" .. getname( ) .. "' (position out of bounds)", 2 ) end local value = list[ pos ] if pos <= last then @@ -172,219 +192,308 @@ end - table.unpack - table.concat - table.sort -]] +--]] --- Environment Context (EXPERIMENTAL) (WIP) (INCOMPLETE) - --- bind to locals to minimise environment recursion +--- bind to locals to minimise environment recursion and improve speed local rawset, rawlen, ModUtil, getmetatable, setmetatable, pairs, ipairs, coroutine, - rawpairs, rawipairs, qrawpairs, qrawipairs, getfenv, setfenv, tostring, xpcall + rawpairs, rawipairs, qrawpairs, qrawipairs, tostring, getfenv, setfenv = rawset, rawlen, ModUtil, getmetatable, setmetatable, pairs, ipairs, coroutine, - rawpairs, rawipairs, qrawpairs, qrawipairs, getfenv, setfenv, tostring, xpcall + rawpairs, rawipairs, qrawpairs, qrawipairs, tostring, getfenv, setfenv -function ModUtil.RawInterface( obj ) +--[[ + local version of toLookup as to not depend on Main.lua +]] +local function toLookup( tableArg ) + local lookup = { } + for _, value in pairs( tableArg ) do + lookup[ value ] = true + end + return lookup +end - local meta = { - __index = function( _, key ) - return rawget( obj, key ) - end, - __newindex = function( _, key, value ) - rawset( obj, key, value ) - end, - __len = function( ) - return rawlen( obj ) - end, - __next = function( _, key ) - return rawnext( obj, key ) - end, - __inext = function( _ , idx ) - return rawinext( obj, idx ) - end, - __pairs = function( ) - return rawpairs( obj ) - end, - __ipairs = function( ) - return rawipairs( obj ) - end - } +-- Type/Syntax Constants - local interface = { } - setmetatable( interface, meta ) - return interface +local passByValueTypes = toLookup{ "number", "boolean", "nil" } +local callableCandidateTypes = toLookup{ "function", "table", "userdata" } -- string excluded because their references are managed +local excludedFieldNames = toLookup{ "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" } -end -local __G = ModUtil.RawInterface( _G ) -__G.__G = __G +-- Environment Manipulation -local surrogateEnvironments = { } -setmetatable( surrogateEnvironments, { __mode = "k" } ) +local _G = _ENV +local __G -local function getenv( ) - local level = 3 - repeat - level = level + 1 - local info = debug.getinfo( level, "f" ) - if info then - local env = surrogateEnvironments[ info.func ] - if env then - return env - end - end - until not info - return __G -end +local threadEnvironments = setmetatable( { }, { __mode = "k" } ) ---[[ - Make lexical environments use locals instead of upvalues -]] -function ModUtil.ReplaceGlobalEnvironment( ) - - local split = function( path ) - if type( path ) == "string" - and path:find("[.]") - and not path:find("[.][.]+") - and not path:find("^[.]") - and not path:find("[.]$") then - return ModUtil.PathToIndexArray( path ) - end - return { path } - end - local get = ModUtil.SafeGet - debug.setmetatable( __G._G, { } ) +local function getEnv( thread ) + return threadEnvironments[ thread or coroutine.running( ) ] or _G +end - local meta = { +local function replaceGlobalEnvironment( ) + __G = debug.setmetatable( { }, { __index = function( _, key ) - local env = getenv( ) - local value = env[ key ] - if value ~= nil then return value end - return get( env, split( key ) ) + return getEnv( )[ key ] end, __newindex = function( _, key, value ) - getenv( )[ key ] = value + getEnv( )[ key ] = value end, __len = function( ) - return #getenv( ) + return #getEnv( ) end, __next = function( _, key ) - return next( getenv( ), key ) + return next( getEnv( ), key ) end, __inext = function( _, key ) - return inext( getenv( ), key ) + return inext( getEnv( ), key ) end, __pairs = function( ) - return pairs( getenv( ) ) + return pairs( getEnv( ) ) end, __ipairs = function( ) - return ipairs( getenv( ) ) + return ipairs( getEnv( ) ) end - } + } ) + _G._G = __G + local reg = debug.getregistry( ) + for i, v in ipairs( reg ) do + if v == _G then reg[ i ] = __G end + end + ModUtil.Identifiers.Inverse._ENV = __G +end + +-- Managed Object Data + +local objectData = setmetatable( { }, { __mode = "k" } ) - debug.setmetatable( __G._G, meta ) +local function newObjectData( data ) + local obj = { } + objectData[ obj ] = data + return obj end -local function skipenv( obj ) - if obj ~= __G._G then return obj end - return __G +local function getObjectData( obj, key ) + return objectData[ obj ][ key ] end --- Management +ModUtil.Metatables.Proxy = { + __index = function( self, key ) + return objectData[ self ][ key ] + end, + __newindex = function( self, key, value ) + objectData[ self ][ key ] = value + end, + __len = function( self, ...) + return #objectdata( self ) + end, + __next = function( self, ... ) + return next( objectData[ self ], ... ) + end, + __inext = function( self, ... ) + return inext( objectData[ self ], ... ) + end, + __pairs = function( self, ... ) + return pairs( objectData[ self ], ... ) + end, + __ipairs = function( self, ... ) + return ipairs( objectData[ self ], ... ) + end +} ---[[ - Create a namespace that can be used for the mod's functions - and data, and ensure that it doesn't end up in save files. +function ModUtil.Proxy( data, meta ) + return setmetatable( newObjectData( data ), meta or ModUtil.Metatables.Proxy ) +end - modName - the name of the mod - parent - the parent mod, or nil if this mod stands alone -]] -function ModUtil.RegisterMod( modName, parent, content ) - if not parent then - parent = _G - SaveIgnores[ modName ] = true - end - local mod = parent[ modName ] - if not mod then - mod = { } - parent[ modName ] = mod - local path = ModUtil.Mods.Index[ parent ] - if path ~= nil then - path = path .. '.' - else - path = '' - end - path = path .. modName - ModUtil.Mods.Table[ path ] = mod - ModUtil.Identifiers.Table[ mod ] = path +ModUtil.Metatables.Raw = { + __index = function( self, ... ) + return rawget( getObjectData( self, "data" ), ... ) + end, + __newindex = function( self, ... ) + return rawset( getObjectData( self, "data" ), ... ) + end, + __len = function( self, ...) + return rawlen( getObjectData( self, "data" ), ... ) + end, + __next = function( self, ... ) + return rawnext( getObjectData( self, "data" ), ... ) + end, + __inext = function( self, ... ) + return rawinext( getObjectData( self, "data" ), ... ) + end, + __pairs = function( self, ... ) + return rawpairs( getObjectData( self, "data" ), ... ) + end, + __ipairs = function( self, ... ) + return rawipairs( getObjectData( self, "data" ), ... ) end - if content then - ModUtil.MapSetTable( parent[ modName ], content ) +} + +function ModUtil.Raw( obj ) + return ModUtil.Proxy( { data = obj }, ModUtil.Metatables.Raw ) +end + +-- Operations on Callables + +ModUtil.Callable = { Func = { } } + +function ModUtil.Callable.Get( obj ) + local meta, pobj + while obj do + local t = type( obj ) + if t == "function" then break end + if not callableCandidateTypes[ t ] then return pobj end + meta = getmetatable( obj ) + if not meta then break end + pobj, obj = obj, rawget( meta, "__call" ) end - return parent[ modName ] + return pobj, obj end ---[[ - Tell each screen anchor that they have been forced closed by the game -]] -function ModUtil.ForceClosed( triggerArgs ) - for _, v in pairs( ModUtil.Anchors.CloseFuncs ) do - v( nil, nil, triggerArgs ) +function ModUtil.Callable.Set( o, f ) + local m = getmetatable( o ) or { } + m.__call = f + return setmetatable( o, m ), f +end + +function ModUtil.Callable.Map( o, f, ... ) + local pobj, obj = ModUtil.Callable.Get( o ) + if not pobj then + return nil, f( obj, ... ) end - ModUtil.Anchors.CloseFuncs = { } - ModUtil.Anchors.Menu = { } + return ModUtil.Callable.Set( pobj, f( obj, ... ) ) +end + +function ModUtil.Callable.Func.Get( ... ) + local _, f = ModUtil.Callable.Get( ... ) + return f +end + +function ModUtil.Callable.Func.Set( ... ) + local _, f = ModUtil.Callable.Set( ... ) + return f +end + +function ModUtil.Callable.Func.Map( ... ) + local _, f = ModUtil.Callable.Map( ... ) + return f end -OnAnyLoad{ function( triggerArgs ) ModUtil.ForceClosed( triggerArgs ) end } -ModUtil.Internal.FuncsToLoad = { } +ModUtil.Callable.Set( ModUtil.Callable, function ( _, obj ) + return ModUtil.Callable.Func.Get( obj ) ~= nil +end ) + +-- Data Misc -function ModUtil.Internal.LoadFuncs( triggerArgs ) - for _, v in pairs( ModUtil.Internal.FuncsToLoad ) do - v( triggerArgs ) +function ModUtil.Args.Map( map, ... ) + local out = { } + local args = table.pack( ... ) + for i = 1, args.n do + out[ i ] = map( args[ i ] ) end - ModUtil.Internal.FuncsToLoad = { } + return table.unpack( out ) end -OnAnyLoad{ function( triggerArgs ) ModUtil.Internal.LoadFuncs( triggerArgs ) end } ---[[ - Run the provided function once on the next in-game load. +function ModUtil.Args.Take( n, ... ) + local args = table.pack( ... ) + return table.unpack( args, 1, n ) +end - triggerFunction - the function to run -]] -function ModUtil.LoadOnce( triggerFunction ) - table.insert( ModUtil.Internal.FuncsToLoad, triggerFunction ) +function ModUtil.Args.Drop( n, ... ) + local args = table.pack( ... ) + return table.unpack( args, n + 1, args.n ) end ---[[ - Cancel running the provided function once on the next in-game load. +function ModUtil.Table.Map( tbl, map ) + local out = { } + for k, v in pairs( tbl ) do + out[ k ] = map( v ) + end + return out +end - triggerFunction - the function to cancel running -]] -function ModUtil.CancelLoadOnce( triggerFunction ) - for i, v in ipairs( ModUtil.Internal.FuncsToLoad ) do - if v == triggerFunction then - table.remove( ModUtil.Internal.FuncsToLoad, i ) - end +function ModUtil.Table.Mutate( tbl, map ) + for k, v in pairs( tbl ) do + tbl[ k ] = map( v ) end end --- Data Misc +function ModUtil.Table.Replace( target, data ) + for k in pairs( target ) do + target[ k ] = data[ k ] + end + for k, v in pairs( data ) do + target[ k ] = v + end +end + +function ModUtil.Table.UnKeyed( tableArg ) + local lk = 0 + for k in pairs( tableArg ) do + if type( k ) ~= "number" then + return false + end + if lk + 1 ~= k then + return false + end + lk = k + end + return true +end -function ModUtil.ReferFunction( funcPath, baseTable ) - if type( baseTable ) == "number" then -- locals - baseTable = ModUtil.Locals( baseTable + 1 ) +function ModUtil.String.Join( sep, ... ) + local out = { } + local args = table.pack( ... ) + out[ 1 ] = args[ 1 ] + for i = 2, args.n do + table.insert( out, sep ) + table.insert( out, args[ i ] ) end - baseTable = baseTable or _G - local indexArray = ModUtil.PathToIndexArray( funcPath ) - return function( ... ) - return ModUtil.SafeGet( baseTable, indexArray )( ... ) + return table.concat( out ) +end + +function ModUtil.String.Chunk( text, chunkSize, maxChunks ) + local chunks = { "" } + local cs = 0 + local ncs = 1 + for chr in text:gmatch( "." ) do + cs = cs + 1 + if cs > chunkSize or chr == "\n" then + ncs = ncs + 1 + if maxChunks and ncs > maxChunks then + return chunks + end + chunks[ ncs ] = "" + cs = 0 + end + if chr ~= "\n" then + chunks[ ncs ] = chunks[ ncs ] .. chr + end end + return chunks +end + +-- String Representations + +ModUtil.ToString = ModUtil.Callable.Set( { }, function( _, o ) + local identifier = o ~= nil and ModUtil.Identifiers.Data[ o ] + identifier = identifier and identifier .. ":" or "" + return identifier .. ModUtil.ToString.Static( o ) +end ) + +function ModUtil.ToString.Address( o ) + local t = type( o ) + if t == "string" or passByValueTypes[ t ] then return end + return tostring( o ):match( ": 0*([0-9A-F]*)" ) end -local passByValueTypes = ToLookup{ "number", "boolean", "nil" } -local excludedFieldNames = ToLookup{ "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" } +function ModUtil.ToString.Static( o ) + local t = type( o ) + if t == "string" or passByValueTypes[ t ] then return tostring( o ) end + return tostring( o ):gsub( ": 0*", ":", 1 ) +end -function ModUtil.ValueString( o ) +function ModUtil.ToString.Value( o ) local t = type( o ) if t == 'string' then return '"' .. o .. '"' @@ -392,45 +501,29 @@ function ModUtil.ValueString( o ) if passByValueTypes[ t ] then return tostring( o ) end - local identifier = ModUtil.Identifiers.Table[ o ] - if identifier then - identifier = "(" .. identifier .. ")" - else - identifier = "" - end - return identifier .. '<' .. tostring( o ) .. '>' + return '<' .. ModUtil.ToString( o ) .. '>' end -function ModUtil.KeyString( o ) +function ModUtil.ToString.Key( o ) local t = type( o ) - o = tostring( o ) - if t == 'string' and not excludedFieldNames[ o ] then - local pattern = "^[a-zA-Z_][a-zA-Z0-9_]*$" - if o:gmatch( pattern ) then + if t == 'string' then + if not excludedFieldNames[ o ] and o:gmatch( "^[a-zA-Z_][a-zA-Z0-9_]*$" ) then return o end return '"' .. o .. '"' end if t == 'number' then - o = "#" .. o + return "#" .. tostring( o ) end - if not passByValueTypes[ t ] then - o = '<' .. o .. '>' - end - local identifier = ModUtil.Identifiers.Table[ o ] - if identifier then - identifier = "(" .. identifier .. ")" - else - identifier = "" - end - return identifier .. o + if passByValueTypes[ t ] then return tostring( o ) end + return '<' .. ModUtil.ToString( o ) .. '>' end -function ModUtil.TableKeysString( o ) +function ModUtil.ToString.TableKeys( o ) if type( o ) == 'table' then local out = { } for k in pairs( o ) do - table.insert( out , ModUtil.KeyString( k ) ) + table.insert( out , ModUtil.ToString.Key( k ) ) table.insert( out , ', ' ) end table.remove( out ) @@ -438,194 +531,127 @@ function ModUtil.TableKeysString( o ) end end -function ModUtil.ToShallowString( o ) +function ModUtil.ToString.Shallow( o ) if type( o ) == "table" then - local out = { ModUtil.ValueString( o ), "{ " } + local out = { ModUtil.ToString.Value( o ), "( " } for k, v in pairs( o ) do - table.insert( out, ModUtil.KeyString( k ) ) + table.insert( out, ModUtil.ToString.Key( k ) ) table.insert( out, ' = ' ) - table.insert( out, ModUtil.ValueString( v ) ) + table.insert( out, ModUtil.ToString.Value( v ) ) table.insert( out , ", " ) end if #out > 2 then table.remove( out ) end - return table.concat( out ) .. " }" + return table.concat( out ) .. " )" else - return ModUtil.ValueString( o ) + return ModUtil.ToString.Value( o ) end end -function ModUtil.ToDeepString( o, seen ) +ModUtil.ToString.Deep = ModUtil.Callable.Set( { }, function( _, o, seen ) seen = seen or { } if type( o ) == "table" and not seen[ o ] then seen[ o ] = true - local out = { ModUtil.ValueString( o ), "{ " } + local out = { ModUtil.ToString.Value( o ), "( " } for k, v in pairs( o ) do - table.insert( out, ModUtil.KeyString( k ) ) + table.insert( out, ModUtil.ToString.Key( k ) ) table.insert( out, ' = ' ) - table.insert( out, ModUtil.ToDeepString( v, seen ) ) + table.insert( out, ModUtil.ToString.Deep( v, seen ) ) table.insert( out , ", " ) end if #out > 2 then table.remove( out ) end - return table.concat( out ) .. " }" + return table.concat( out ) .. " )" else - return ModUtil.ValueString( o ) + return ModUtil.ToString.Value( o ) end +end ) + +local function isNamespace( obj ) + return obj == _G or obj == _ENV or obj == objectData or ModUtil.Mods.Inverse[ obj ] + or ( getmetatable( obj ) == ModUtil.Metatables.Raw and isNamespace( getObjectData( obj, "data" ) ) ) end -function ModUtil.ToDeepNoNamespacesString( o, seen ) +function ModUtil.ToString.Deep.NoNamespaces( o, seen ) local first = false if not seen then first = true seen = { } end - if type( o ) == "table" and not seen[ o ] and o ~= __G._G and ( first or not ModUtil.Mods.Index[ o ] ) then + if type( o ) == "table" and not seen[ o ] and not isNamespace( o ) then seen[ o ] = true - local out = { ModUtil.ValueString( o ), "{ " } + local out = { ModUtil.ToString.Value( o ), "( " } for k, v in pairs( o ) do - if v ~= __G._G and not ModUtil.Mods.Index[ v ] then - table.insert( out, ModUtil.KeyString( k ) ) + if not isNamespace( v ) then + table.insert( out, ModUtil.ToString.Key( k ) ) table.insert( out, ' = ' ) - table.insert( out, ModUtil.ToDeepNoNamespacesString( v, seen ) ) + table.insert( out, ModUtil.ToString.Deep.NoNamespaces( v, seen ) ) table.insert( out , ", " ) end end if #out > 2 then table.remove( out ) end - return table.concat( out ) .. " }" + return table.concat( out ) .. " )" else - return ModUtil.ValueString( o ) + return ModUtil.ToString.Value( o ) end end -function ModUtil.ToDeepNamespacesString( o, seen ) +function ModUtil.ToString.Deep.Namespaces( o, seen ) local first = false if not seen then first = true seen = { } end - if type( o ) == "table" and not seen[ o ] and ( first or o == __G._G or ModUtil.Mods.Index[ o ] ) then + if type( o ) == "table" and not seen[ o ] and isNamespace( o ) then seen[ o ] = true - local out = { ModUtil.ValueString( o ), "{ " } + local out = { ModUtil.ToString.Value( o ), "( " } for k, v in pairs( o ) do - if v == __G._G or ModUtil.Mods.Index[ v ] then - table.insert( out, ModUtil.KeyString( k ) ) + if isNamespace( v ) then + table.insert( out, ModUtil.ToString.Key( k ) ) table.insert( out, ' = ' ) - table.insert( out, ModUtil.ToDeepNamespacesString( v, seen ) ) + table.insert( out, ModUtil.ToString.Deep.Namespaces( v, seen ) ) table.insert( out , ", " ) end end if #out > 2 then table.remove( out ) end - return table.concat( out ) .. " }" + return table.concat( out ) .. " )" else - return ModUtil.ValueString( o ) - end -end - -function ModUtil.MapVars( mapFunc, ... ) - local out = {} - for _, v in ipairs{ ... } do - table.insert( out, mapFunc( v ) ) - end - return table.unpack( out ) -end - -function ModUtil.MapTable( mapFunc, tableArg ) - local out = {} - for k, v in pairs( tableArg ) do - out[ k ] = mapFunc( v ) - end - return out -end - -function ModUtil.JoinStrings( sep, ... ) - local out = {} - local args = { ... } - local i - i, out[ 1 ] = inext( args ) - for _, v in inext, args, i do - table.insert( out, sep ) - table.insert( out, v ) - end - return table.concat( out ) -end - -function ModUtil.ChunkText( text, chunkSize, maxChunks ) - local chunks = { "" } - local cs = 0 - local ncs = 1 - for chr in text:gmatch( "." ) do - cs = cs + 1 - if cs > chunkSize or chr == "\n" then - ncs = ncs + 1 - if maxChunks and ncs > maxChunks then - return chunks - end - chunks[ ncs ] = "" - cs = 0 - end - if chr ~= "\n" then - chunks[ ncs ] = chunks[ ncs ] .. chr - end + return ModUtil.ToString.Value( o ) end - return chunks end -function ModUtil.ReplaceTable( target, data ) - for k in pairs( target ) do - target[ k ] = data[ k ] - end - for k, v in pairs( data ) do - target[ k ] = v - end -end +-- Print -function ModUtil.IsUnKeyed( tableArg ) - local lk = 0 - for k in pairs( tableArg ) do - if type( k ) ~= "number" then - return false - end - if lk + 1 ~= k then - return false +ModUtil.Print = ModUtil.Callable.Set( { }, function ( _, ... ) + print( ... ) + if DebugPrint then ModUtil.Print.Debug( ... ) end + if io then + if io.stdout ~= io.output( ) then + ModUtil.Print.ToFile( io.output( ), ... ) end - lk = k + io.flush( ) end - return true -end - --- Printing +end ) -function ModUtil.PrintToFile( file, ... ) +function ModUtil.Print.ToFile( file, ... ) local close = false if type( file ) == "string" and io then file = io.open( file, "a" ) close = true end - file:write( ModUtil.MapVars( tostring, ... ) ) + file:write( ModUtil.Args.Map( tostring, ... ) ) if close then file:close( ) end end -function ModUtil.DebugPrint( ... ) - local text = ModUtil.JoinStrings( "\t", ModUtil.MapVars( tostring, ... ) ):gsub( "\t", " " ) +function ModUtil.Print.Debug( ... ) + local text = ModUtil.String.Join( "\t", ModUtil.Args.Map( tostring, ... ) ):gsub( "\t", " " ) for line in text:gmatch( "([^\n]+)" ) do DebugPrint{ Text = line } end end -function ModUtil.Print( ... ) - print( ... ) - if DebugPrint then ModUtil.DebugPrint( ... ) end - if io then - if io.stdout ~= io.output( ) then - ModUtil.PrintToFile( io.output( ), ... ) - end - io.flush( ) - end -end - -function ModUtil.PrintTraceback( level ) - level = ( level or 1 ) +function ModUtil.Print.Traceback( level ) + level = level or 1 ModUtil.Print("Traceback:") local cont = true while cont do @@ -652,50 +678,48 @@ function ModUtil.PrintTraceback( level ) end end -function ModUtil.PrintDebugInfo( level ) - level = ( level or 1 ) +function ModUtil.Print.DebugInfo( level ) + level = level or 1 local text - text = ModUtil.ToDeepString( debug.getinfo( level + 1 ) ) + text = ModUtil.ToString.Deep( debug.getinfo( level + 1 ) ) ModUtil.Print( "Debug Info:" .. "\t" .. text:sub( 1 + text:find( ">" ) ) ) end -function ModUtil.PrintNamespaces( level ) - level = ( level or 1 ) +function ModUtil.Print.Namespaces( level ) + level = level or 1 local text ModUtil.Print("Namespaces:") - text = ModUtil.ToDeepNamespacesString( ModUtil.Locals( level + 1 ) ) + text = ModUtil.ToString.Deep.Namespaces( ModUtil.Locals( level + 1 ) ) ModUtil.Print( "\t" .. "Locals:" .. "\t" .. text:sub( 1 + text:find( ">" ) ) ) - text = ModUtil.ToDeepNamespacesString( ModUtil.UpValues( level + 1 ) ) + text = ModUtil.ToString.Deep.Namespaces( ModUtil.UpValues( level + 1 ) ) ModUtil.Print( "\t" .. "UpValues:" .. "\t" .. text:sub( 1 + text:find( ">" ) ) ) - local func = debug.getinfo( level + 1, "f" ).func - text = ModUtil.ToDeepNamespacesString( surrogateEnvironments[ func ] or getfenv( func ) ) - ModUtil.Print( "\t" .. "Globals:" .. "\t" .. text ) + text = ModUtil.ToString.Deep.Namespaces( _ENV ) + ModUtil.Print( "\t" .. "Globals:" .. "\t" .. text:sub( 1 + text:find( ">" ) ) ) end -function ModUtil.PrintVariables( level ) - level = ( level or 1 ) +function ModUtil.Print.Variables( level ) + level = level or 1 local text ModUtil.Print("Variables:") - text = ModUtil.ToDeepNoNamespacesString( ModUtil.Locals( level + 1 ) ) + text = ModUtil.ToString.Deep.NoNamespaces( ModUtil.Locals( level + 1 ) ) ModUtil.Print( "\t" .. "Locals:" .. "\t" .. text:sub( 1 + text:find( ">" ) ) ) - text = ModUtil.ToDeepNoNamespacesString( ModUtil.UpValues( level + 1 ) ) + text = ModUtil.ToString.Deep.NoNamespaces( ModUtil.UpValues( level + 1 ) ) ModUtil.Print( "\t" .. "UpValues:" .. "\t" .. text:sub( 1 + text:find( ">" ) ) ) - local func = debug.getinfo( level + 1, "f" ).func - text = ModUtil.ToDeepNoNamespacesString( surrogateEnvironments[ func ] or getfenv( func ) ) - ModUtil.Print( "\t" .. "Globals:" .. "\t" .. text ) + text = ModUtil.ToString.Deep.NoNamespaces( _ENV ) + ModUtil.Print( "\t" .. "Globals:" .. "\t" .. text:sub( 1 + text:find( ">" ) ) ) end --[[ Call a function with the provided arguments instead of halting when an error occurs it prints the entire error traceback -]] +--]] function ModUtil.DebugCall( f, ... ) return xpcall( f, function( err ) ModUtil.Print( err ) - ModUtil.PrintDebugInfo( 2 ) - ModUtil.PrintNamespaces( 2 ) - ModUtil.PrintVariables( 2 ) - ModUtil.PrintTraceback( 2 ) + ModUtil.Print.DebugInfo( 2 ) + ModUtil.Print.Namespaces( 2 ) + ModUtil.Print.Variables( 2 ) + ModUtil.Print.Traceback( 2 ) end, ... ) end @@ -704,12 +728,12 @@ end --[[ Return a slice of an array table, python style would be written state[ start : stop : step ] in python - + start and stop are offsets rather than ordinals meaning 0 corresponds to the start of the array and -1 corresponds to the end -]] -function ModUtil.Slice( state, start, stop, step ) +--]] +function ModUtil.Array.Slice( state, start, stop, step ) local slice = { } local n = #state start = start or 0 @@ -726,167 +750,71 @@ function ModUtil.Slice( state, start, stop, step ) return slice end -local function ShallowCopyTable( orig ) - -- from UtilityScripts.lua - if orig == nil then - return - end - - local copy = { } - for k, v in pairs( orig ) do - copy[ k ] = v - end - return copy +function ModUtil.Array.Copy( a ) + return { table.unpack( a ) } end -local function DeepCopyTable( orig ) - -- from UtilityScripts.lua - local orig_type = type( orig ) - local copy - if orig_type == 'table' then - copy = { } - -- slightly more efficient to call next directly instead of using pairs - for k, v in next, orig, nil do - copy[ k ] = DeepCopyTable( v ) - end - else - copy = orig +--[[ + Concatenates arrays, in order. + + a, ... - the arrays +--]] +function ModUtil.Array.Join( a, ... ) + local b = ... + if not b then return ModUtil.Array.Copy( a ) end + local c = { } + local j = 0 + for i, v in ipairs( a ) do + c[ i ] = v + j = i end - return copy + for i, v in ipairs( b ) do + c[ i + j ] = v + end + return ModUtil.Array.Join( c, ModUtil.Args.Drop( 1, ... ) ) end -ModUtil.Internal.MarkedForCollapse = { } +ModUtil.Table.Copy = ModUtil.Callable.Set( { }, function( _, t ) + c = { } + for k, v in pairs( t ) do + c[ k ] = v + end + return c +end ) -function ModUtil.CollapseTable( tableArg ) - local collapsedTable = { } - local usedIndices = {} - local i = 1 - repeat - collapsedTable[ i ] = tableArg[ i ] - usedIndices[ i ] = true - i = i + 1 - until i > #tableArg - for k, v in pairs( tableArg ) do - if not usedIndices[ k ] then - collapsedTable[ i ] = v - i = i + 1 +function ModUtil.Table.Copy.Deep( t ) + c = { } + for k, v in pairs( t ) do + if type( v ) == "table" then + v = ModUtil.Table.Copy( v ) end + c[ k ] = v end - return collapsedTable + return c end -function ModUtil.CollapseTableInPlace( tableArg ) - local collapsedTable = ModUtil.CollapseTable( tableArg ) - for k in pairs( tableArg ) do - tableArg[ k ] = nil - end - for i, v in pairs( collapsedTable ) do - tableArg[ i ] = v +function ModUtil.Table.Clear( t ) + for k in pairs( t ) do + t[ k ] = nil end + return t end -function ModUtil.CollapseMarked( ) - for tbl, state in pairs( ModUtil.Internal.MarkedForCollapse ) do - if state then - ModUtil.CollapseTableInPlace( tbl ) - end - end - ModUtil.Internal.MarkedForCollapse = { } -end -OnAnyLoad{ ModUtil.CollapseMarked } - -function ModUtil.MarkForCollapse( tableArg ) - ModUtil.Internal.MarkedForCollapse[ tableArg ] = true -end - -function ModUtil.UnmarkForCollapse( tableArg ) - ModUtil.Internal.MarkedForCollapse[ tableArg ] = false -end - ---[[ - Safely create a new empty table at Table.key and return it. - - Table - the table to modify - key - the key at which to store the new empty table -]] -function ModUtil.NewTable( tableArg, key ) - local nodeType = ModUtil.Nodes.Index[ key ] - if nodeType then - return ModUtil.Nodes.Table[ nodeType ].New( tableArg ) - end - local tbl = tableArg[ key ] - if type( tbl ) ~= "table" then - tbl = { } - tableArg[ key ] = tbl - end - return tbl -end - ---[[ - Safely retrieve the a value from deep inside a table, given - an array of indices into the table. - - For example, if indexArray is { "a", 1, "c" }, then - Table[ "a" ][ 1 ][ "c" ] is returned. If any of Table[ "a" ], - Table[ "a" ][ 1 ], or Table[ "a" ][ 1 ][ "c" ] are nil, then nil - is returned instead. - - Table - the table to retrieve from - indexArray - the list of indices -]] -function ModUtil.SafeGet( baseTable, indexArray ) - local node = baseTable - for _, key in ipairs( indexArray ) do - if type( node ) ~= "table" then - return nil - end - local nodeType = ModUtil.Nodes.Index[ key ] - if nodeType then - node = ModUtil.Nodes.Table[ nodeType ].Get( node ) - else - node = node[ key ] - end +function ModUtil.Table.Transpose( t ) + local i = { } + for k, v in pairs( t ) do + i[ v ] = k end - return node + return i end ---[[ - Safely set a value deep inside a table, given an array of - indices into the table, and creating any necessary tables - along the way. - - For example, if indexArray is { "a", 1, "c" }, then - Table[ "a" ][ 1 ][ "c" ] = Value once this function returns. - If any of Table[ "a" ] or Table[ "a" ][ 1 ] does not exist, they - are created. - - baseTable - the table to set the value in - indexArray - the list of indices - value - the value to add -]] -function ModUtil.SafeSet( baseTable, indexArray, value ) - if next( indexArray ) == nil then - return false -- can't set the input argument - end - local n = #indexArray -- change to shallow copy + table.remove later - local node = baseTable - for i = 1, n - 1 do - local key = indexArray[ i ] - if not ModUtil.NewTable( node, key ) then return false end - local nodeType = ModUtil.Nodes.Index[ key ] - if nodeType then - node = ModUtil.Nodes.Table[ nodeType ].Get( node ) - else - node = node[ key ] - end +function ModUtil.Table.Flip( t ) + local i = ModUtil.Table.Transpose( t ) + ModUtil.Table.Clear( t ) + for k, v in pairs( i ) do + t[ k ] = v end - local key = indexArray[ n ] - local nodeType = ModUtil.Nodes.Index[ key ] - if nodeType then - return ModUtil.Nodes.Table[ nodeType ].Set( node, value ) - end - node[ key ] = value - return true + return t end --[[ @@ -920,16 +848,17 @@ end InnerBar = nil } } -]] -function ModUtil.MapNilTable( inTable, nilTable ) +--]] +function ModUtil.Table.NilMerge( inTable, nilTable ) for nilKey, nilVal in pairs( nilTable ) do local inVal = inTable[ nilKey ] if type( nilVal ) == "table" and type( inVal ) == "table" then - ModUtil.MapNilTable( inVal, nilVal ) + ModUtil.Table.NilMerge( inVal, nilVal ) else inTable[ nilKey ] = nil end end + return inTable end --[[ @@ -958,47 +887,115 @@ end InnerBar = 8 } } -]] -function ModUtil.MapSetTable( inTable, setTable ) +--]] +function ModUtil.Table.Merge( inTable, setTable ) for setKey, setVal in pairs( setTable ) do local inVal = inTable[ setKey ] if type( setVal ) == "table" and type( inVal ) == "table" then - ModUtil.MapSetTable( inVal, setVal ) + ModUtil.Table.Merge( inVal, setVal ) else inTable[ setKey ] = setVal end end + return inTable end --- Path Manipulation +-- Index Array Manipulation --[[ - Concatenates two index arrays, in order. + Safely retrieve the a value from deep inside a table, given + an array of indices into the table. - a, b - the index arrays -]] -function ModUtil.JoinIndexArrays( a, b ) - local c = { } - local j = 0 - for i, v in ipairs( a ) do - c[ i ] = v - j = i + For example, if indexArray is { "a", 1, "c" }, then + Table[ "a" ][ 1 ][ "c" ] is returned. If any of Table[ "a" ], + Table[ "a" ][ 1 ], or Table[ "a" ][ 1 ][ "c" ] are nil, then nil + is returned instead. + + Table - the table to retrieve from + indexArray - the list of indices +--]] +function ModUtil.IndexArray.Get( baseTable, indexArray ) + local node = baseTable + for _, key in ipairs( indexArray ) do + if type( node ) ~= "table" then + return nil + end + local nodeType = ModUtil.Node.Inverse[ key ] + if nodeType then + node = ModUtil.Node.Data[ nodeType ].Get( node ) + else + node = node[ key ] + end end - for i, v in ipairs( b ) do - c[ i + j ] = v + return node +end + +--[[ + Safely set a value deep inside a table, given an array of + indices into the table, and creating any necessary tables + along the way. + + For example, if indexArray is { "a", 1, "c" }, then + Table[ "a" ][ 1 ][ "c" ] = Value once this function returns. + If any of Table[ "a" ] or Table[ "a" ][ 1 ] does not exist, they + are created. + + baseTable - the table to set the value in + indexArray - the list of indices + value - the value to add +--]] +function ModUtil.IndexArray.Set( baseTable, indexArray, value ) + if next( indexArray ) == nil then + return false -- can't set the input argument end - return c + local n = #indexArray -- change to shallow copy + table.remove later + local node = baseTable + for i = 1, n - 1 do + local key = indexArray[ i ] + if not ModUtil.Node.New( node, key ) then return false end + local nodeType = ModUtil.Node.Inverse[ key ] + if nodeType then + node = ModUtil.Node.Data[ nodeType ].Get( node ) + else + node = node[ key ] + end + end + local key = indexArray[ n ] + local nodeType = ModUtil.Node.Inverse[ key ] + if nodeType then + return ModUtil.Node.Data[ nodeType ].Set( node, value ) + end + node[ key ] = value + return true +end + +function ModUtil.IndexArray.Map( baseTable, indexArray, map, ... ) + return ModUtil.IndexArray.Set( baseTable, indexArray, map( ModUtil.IndexArray.Get( baseTable, indexArray ), ... ) ) +end + +-- Path Manipulation + +function ModUtil.Path.Join( p, ... ) + local q = ... + if not q then return p end + if p == '' then return ModUtil.Path.Join( q, ModUtil.Args.Drop( 1, ... ) ) end + if q == '' then return ModUtil.Path.Join( p, ModUtil.Args.Drop( 1, ... ) ) end + return ModUtil.Path.Join( p .. '.' .. q, ModUtil.Args.Drop( 1, ... ) ) +end + +function ModUtil.Path.Map( path, map, ... ) + return ModUtil.IndexArray.Map( _ENV, ModUtil.Path.IndexArray( path ), map, ... ) end --[[ Create an index array from the provided Path. The returned array can be used as an argument to the safe table - manipulation functions, such as ModUtil.SafeSet and ModUtil.SafeGet. + manipulation functions, such as ModUtil.IndexArray.Set and ModUtil.IndexArray.Get. path - a dot-separated string that represents a path into a table -]] -function ModUtil.PathToIndexArray( path ) +--]] +function ModUtil.Path.IndexArray( path ) if type( path ) == "table" then return path end -- assume index array is given local s = "" local i = { } @@ -1019,55 +1016,55 @@ end --[[ Safely get a value from a Path. - For example, ModUtil.PathGet( "a.b.c" ) returns a.b.c. + For example, ModUtil.Path.Get( "a.b.c" ) returns a.b.c. If either a or a.b is nil, nil is returned instead. path - the path to get the value base - (optional) The table to retreive the value from. If not provided, retreive a global. -]] -function ModUtil.PathGet( path, base ) - return ModUtil.SafeGet( base or _G, ModUtil.PathToIndexArray( path ) ) +--]] +function ModUtil.Path.Get( path, base ) + return ModUtil.IndexArray.Get( base or _ENV, ModUtil.Path.IndexArray( path ) ) end --[[ Safely get set a value to a Path. - For example, ModUtil.PathSet( "a.b.c", 1 ) sets a.b.c = 1. + For example, ModUtil.Path.Set( "a.b.c", 1 ) sets a.b.c = 1. If either a or a.b is nil, they are created. path - the path to get the value base - (optional) The table to retreive the value from. If not provided, retreive a global. -]] -function ModUtil.PathSet( path, value, base ) - return ModUtil.SafeSet( base or _G, ModUtil.PathToIndexArray( path ), value ) +--]] +function ModUtil.Path.Set( path, value, base ) + return ModUtil.IndexArray.Set( base or _ENV, ModUtil.Path.IndexArray( path ), value ) end --- Metaprogramming Shenanigans (EXPERIMENTAL) (WIP) +-- Metaprogramming Shenanigans local stackLevelProperty stackLevelProperty = { here = function( self ) - local thread = rawget( self, "thread" ) - local cursize = rawget( self, "level" ) + 1 + local thread = getObjectData( self, "thread" ) + local cursize = getObjectData( self, "level" ) + 1 while debug.getinfo( thread, cursize, "f" ) do cursize = cursize + 1 end - return cursize - rawget( self, "size" ) - 1 + return cursize - getObjectData( self, "size" ) - 1 end, top = function( self ) - local thread = rawget( self, "thread" ) - local level = rawget( self, "level" ) + local thread = getObjectData( self, "thread" ) + local level = getObjectData( self, "level" ) local cursize = level + 1 while debug.getinfo( thread, cursize, "f" ) do cursize = cursize + 1 end return cursize - level - 1 end, - there = function( self ) return rawget( self, "level" ) end, - bottom = function( self ) return rawget( self, "size" ) end, - co = function( self ) return rawget( self, "thread" ) end, + there = function( self ) return getObjectData( self, "level" ) end, + bottom = function( self ) return getObjectData( self, "size" ) end, + co = function( self ) return getObjectData( self, "thread" ) end, func = function( self ) return debug.getinfo( self.co, self.here, "f" ).func end @@ -1100,12 +1097,6 @@ local stackLevelFunction = { end, upvaluejoin = function( self, ... ) return debug.upvaluejoin( self.func, ... ) - end, - getfenv = function( self, ... ) - return getfenv( self.func, ... ) - end, - setfenv = function( self, ... ) - return setfenv( self.func, ... ) end } @@ -1148,9 +1139,9 @@ ModUtil.Metatables.StackLevel = { return function( ) end, self end, __eq = function( self, other ) - return rawget( self, "thread" ) == rawget( other, "thread" ) - and rawget( self, "size" ) == rawget( other, "size") - and rawget( self, "level" ) == rawget( other, "level") + return getObjectData( self, "thread" ) == getObjectData( other, "thread" ) + and getObjectData( self, "size" ) == getObjectData( other, "size") + and getObjectData( self, "level" ) == getObjectData( other, "level") end } @@ -1164,19 +1155,17 @@ function ModUtil.StackLevel( level ) end size = size - level - 1 if size > 0 then - local stackLevel = { level = level, size = size, thread = thread } - setmetatable( stackLevel, ModUtil.Metatables.StackLevel ) - return stackLevel + return ModUtil.Proxy( { level = level, size = size, thread = thread }, ModUtil.Metatables.StackLevel ) end end ModUtil.Metatables.StackLevels = { __index = function( self, level ) - return ModUtil.StackLevel( ( level or 0 ) + rawget( self, "level" ).here ) + return ModUtil.StackLevel( ( level or 0 ) + getObjectData( self, "level" ).here ) end, - __newindex = function() end, + __newindex = function( ) end, __len = function( self ) - return rawget( self, "level" ).bottom + return getObjectData( self, "level" ).bottom end, __next = function( self, level ) level = ( level or 0 ) + 1 @@ -1193,12 +1182,74 @@ ModUtil.Metatables.StackLevels.__ipairs = ModUtil.Metatables.StackLevels.__pairs ModUtil.Metatables.StackLevels.__inext = ModUtil.Metatables.StackLevels.__next function ModUtil.StackLevels( level ) - local levels = { level = ModUtil.StackLevel( level or 0 ) } - setmetatable( levels, ModUtil.Metatables.StackLevels ) - return levels + return ModUtil.Proxy( { level = ModUtil.StackLevel( level or 0 ) }, ModUtil.Metatables.StackLevels ) end -local excludedUpValueNames = ToLookup{ "_ENV" } + +local excludedUpValueNames = toLookup{ "_ENV" } + +ModUtil.Metatables.UpValues = { + __index = function( self, name ) + if excludedUpValueNames[ name ] then return end + local func = getObjectData( self, "func" ) + local idx = 0 + repeat + idx = idx + 1 + local n, value = debug.getupvalue( func, idx ) + if n == name then + return value + end + until not n + end, + __newindex = function( self, name, value ) + if excludedUpValueNames[ name ] then return end + local func = getObjectData( self, "func" ) + local idx = name and 0 or -1 + repeat + idx = idx + 1 + local n = debug.getupvalue( func, idx ) + if n == name then + debug.setupvalue( func, idx, value ) + return + end + until not n + end, + __len = function( ) + return 0 + end, + __next = function( self, name ) + local func = getObjectData( self, "func" ) + local idx = name and 0 or -1 + repeat + idx = idx + 1 + local n = debug.getupvalue( func, idx ) + if n == name then + local value + repeat + idx = idx + 1 + n, value = debug.getupvalue( func, idx ) + if n and not excludedUpValueNames[ n ] then + return n, value + end + until not n + end + until not n + end, + __inext = function( ) end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return function( ) end, self + end +} + +ModUtil.UpValues = ModUtil.Callable.Set( { }, function( _, func ) + if type( func ) ~= "function" then + func = debug.getinfo( ( func or 1 ) + 1, "f" ).func + end + return ModUtil.Proxy( { func = func }, ModUtil.Metatables.UpValues ) +end ) local idData = { } setmetatable( idData, { __mode = "k" } ) @@ -1211,7 +1262,7 @@ end local function setUpValueIdData( id, func, idx ) local tbl = idData[ id ] if not tbl then - tbl = {} + tbl = { } idData[ id ] = tbl end tbl.func, tbl.idx = func, idx @@ -1224,9 +1275,9 @@ function debug.upvaluejoin( f1, n1, f2, n2 ) setUpValueIdData( debug.upvalueid( f1, n1 ), f2, n2 ) end -ModUtil.Metatables.UpValueIds = { +ModUtil.Metatables.UpValues.Ids = { __index = function( self, idx ) - local func = rawget( self, "func" ) + local func = getObjectData( self, "func" ) local name = debug.getupvalue( func, idx ) if name and not excludedUpValueNames[ name ] then local id = debug.upvalueid( func, idx ) @@ -1235,7 +1286,7 @@ ModUtil.Metatables.UpValueIds = { end end, __newindex = function( self, idx, value ) - local func = rawget( self, "func" ) + local func = getObjectData( self, "func" ) local name = debug.getupvalue( func, idx ) if name and not excludedUpValueNames[ name ] then local func2, idx2 = getUpValueIdData( value ) @@ -1244,10 +1295,10 @@ ModUtil.Metatables.UpValueIds = { end end, __len = function( self ) - return debug.getinfo( rawget( self, "func" ), 'u' ).nups + return debug.getinfo( getObjectData( self, "func" ), 'u' ).nups end, __next = function ( self, idx ) - local func = rawget( self, "func" ) + local func = getObjectData( self, "func" ) idx = idx or 0 local name while true do @@ -1263,27 +1314,25 @@ ModUtil.Metatables.UpValueIds = { return qrawpairs( self ) end } -ModUtil.Metatables.UpValueIds.__inext = ModUtil.Metatables.UpValueIds.__next -ModUtil.Metatables.UpValueIds.__ipairs = ModUtil.Metatables.UpValueIds.__pairs +ModUtil.Metatables.UpValues.Ids.__inext = ModUtil.Metatables.UpValues.Ids.__next +ModUtil.Metatables.UpValues.Ids.__ipairs = ModUtil.Metatables.UpValues.Ids.__pairs -function ModUtil.UpValueIds( func ) +function ModUtil.UpValues.Ids( func ) if type(func) ~= "function" then func = debug.getinfo( ( func or 1 ) + 1, "f" ).func end - local ups = { func = func } - setmetatable( ups, ModUtil.Metatables.UpValueIds ) - return ups + return ModUtil.Proxy( { func = func }, ModUtil.Metatables.UpValues.Ids ) end -ModUtil.Metatables.UpValueValues = { +ModUtil.Metatables.UpValues.Values = { __index = function( self, idx ) - local name, value = debug.getupvalue( rawget( self, "func" ), idx ) + local name, value = debug.getupvalue( getObjectData( self, "func" ), idx ) if name and not excludedUpValueNames[ name ] then return value end end, __newindex = function( self, idx, value ) - local func = rawget( self, "func" ) + local func = getObjectData( self, "func" ) local name = debug.getupvalue( func, idx ) if name and not excludedUpValueNames[ name ] then debug.setupvalue( func, idx, value ) @@ -1291,10 +1340,10 @@ ModUtil.Metatables.UpValueValues = { end end, __len = function( self ) - return debug.getinfo( rawget( self, "func" ), 'u' ).nups + return debug.getinfo( getObjectData( self, "func" ), 'u' ).nups end, __next = function ( self, idx ) - local func = rawget( self, "func" ) + local func = getObjectData( self, "func" ) idx = idx or 0 local name, value while true do @@ -1306,31 +1355,29 @@ ModUtil.Metatables.UpValueValues = { end end end, - __pairs = ModUtil.Metatables.UpValueIds.__pairs, - __ipairs = ModUtil.Metatables.UpValueIds.__ipairs + __pairs = ModUtil.Metatables.UpValues.Ids.__pairs, + __ipairs = ModUtil.Metatables.UpValues.Ids.__ipairs } -ModUtil.Metatables.UpValueValues.__inext = ModUtil.Metatables.UpValueValues.__next +ModUtil.Metatables.UpValues.Values.__inext = ModUtil.Metatables.UpValues.Values.__next -function ModUtil.UpValueValues( func ) +function ModUtil.UpValues.Values( func ) if type(func) ~= "function" then func = debug.getinfo( ( func or 1 ) + 1, "f" ).func end - local ups = { func = func } - setmetatable( ups, ModUtil.Metatables.UpValueValues ) - return ups + return ModUtil.Proxy( { func = func }, ModUtil.Metatables.UpValues.Values ) end -ModUtil.Metatables.UpValueNames = { +ModUtil.Metatables.UpValues.Names = { __index = function( self, idx ) - local name = debug.getupvalue( rawget( self, "func" ), idx ) + local name = debug.getupvalue( getObjectData( self, "func" ), idx ) if name and not excludedUpValueNames[ name ] then return name end end, __newindex = function( ) end, - __len = ModUtil.Metatables.UpValueValues.__len, + __len = ModUtil.Metatables.UpValues.Values.__len, __next = function ( self, idx ) - local func = rawget( self, "func" ) + local func = getObjectData( self, "func" ) idx = idx or 0 local name while true do @@ -1342,127 +1389,51 @@ ModUtil.Metatables.UpValueNames = { end end end, - __pairs = ModUtil.Metatables.UpValueIds.__pairs, - __ipairs = ModUtil.Metatables.UpValueIds.__ipairs + __pairs = ModUtil.Metatables.UpValues.Ids.__pairs, + __ipairs = ModUtil.Metatables.UpValues.Ids.__ipairs } -ModUtil.Metatables.UpValueNames.__inext = ModUtil.Metatables.UpValueNames.__next +ModUtil.Metatables.UpValues.Names.__inext = ModUtil.Metatables.UpValues.Names.__next -function ModUtil.UpValueNames( func ) +function ModUtil.UpValues.Names( func ) if type(func) ~= "function" then func = debug.getinfo( ( func or 1 ) + 1, "f" ).func end - local ups = { func = func } - setmetatable( ups, ModUtil.Metatables.UpValueNames ) - return ups + return ModUtil.Proxy( { func = func }, ModUtil.Metatables.UpValues.Names ) end -ModUtil.Metatables.UpValues = { +ModUtil.Metatables.UpValues.Stacked = { __index = function( self, name ) if excludedUpValueNames[ name ] then return end - local func = rawget( self, "func" ) - local idx = 0 - repeat - idx = idx + 1 - local n, value = debug.getupvalue( func, idx ) - if n == name then - return value - end - until not n + for _, level in pairs( getObjectData( self, "levels" ) ) do + local idx = 0 + repeat + idx = idx + 1 + local n, v = level.getupvalue( idx ) + if n == name then + return v + end + until not n + end end, __newindex = function( self, name, value ) if excludedUpValueNames[ name ] then return end - local func = rawget( self, "func" ) - local idx = name and 0 or -1 - repeat - idx = idx + 1 - local n = debug.getupvalue( func, idx ) - if n == name then - debug.setupvalue( func, idx, value ) - return - end - until not n + for _, level in pairs( getObjectData( self, "levels" ) ) do + local idx = 0 + repeat + idx = idx + 1 + local n = level.getupvalue( idx ) + if n == name then + level.setupvalue( idx, value ) + return + end + until not n + end end, - __len = function ( self ) - return 0 - end, - __next = function ( self, name ) - local func = rawget( self, "func" ) - local idx = name and 0 or -1 - repeat - idx = idx + 1 - local n = debug.getupvalue( func, idx ) - if n == name then - local value - repeat - idx = idx + 1 - n, value = debug.getupvalue( func, idx ) - if n and not excludedUpValueNames[ n ] then - return n, value - end - until not n - end - until not n - end, - __inext = function() end, - __pairs = function( self ) - return qrawpairs( self ) - end, - __ipairs = function( self ) - return function() end, self - end -} - ---[[ - Return a table representing the upvalues of a function. - - Upvalues are those variables captured by a function from it's - creation context. For example, locals defined in the same file - as the function are accessible to the function as upvalues. - - func - the function to get upvalues from -]] -function ModUtil.UpValues( func ) - if type(func) ~= "function" then - func = debug.getinfo( ( func or 1 ) + 1, "f" ).func - end - local upValues = { func = func } - setmetatable( upValues, ModUtil.Metatables.UpValues ) - return upValues -end - -ModUtil.Metatables.StackedUpValues = { - __index = function( self, name ) - if excludedUpValueNames[ name ] then return end - for _, level in pairs( rawget( self, "levels" ) ) do - local idx = 0 - repeat - idx = idx + 1 - local n, v = level.getupvalue( idx ) - if n == name then - return v - end - until not n - end - end, - __newindex = function( self, name, value ) - if excludedUpValueNames[ name ] then return end - for _, level in pairs( rawget( self, "levels" ) ) do - local idx = 0 - repeat - idx = idx + 1 - local n = level.getupvalue( idx ) - if n == name then - level.setupvalue( idx, value ) - return - end - until not n - end - end, - __len = function( ) + __len = function( ) return 0 end, __next = function( self, name ) - local levels = rawget( self, "levels" ) + local levels = getObjectData( self, "levels" ) for _, level in pairs( levels ) do local idx = name and 0 or -1 repeat @@ -1486,21 +1457,79 @@ ModUtil.Metatables.StackedUpValues = { return qrawpairs( self ), self end, __ipairs = function( self ) - return function() end, self + return function( ) end, self end } -function ModUtil.StackedUpValues( level ) - local upValues = { levels = ModUtil.StackLevels( ( level or 1 ) ) } - setmetatable( upValues, ModUtil.Metatables.StackedUpValues ) - return upValues +function ModUtil.UpValues.Stacked( level ) + return ModUtil.Proxy( { levels = ModUtil.StackLevels( ( level or 1 ) ) }, ModUtil.Metatables.UpValues.Stacked ) end -local excludedLocalNames = ToLookup{ "(*temporary)", "(for generator)", "(for state)", "(for control)" } +local excludedLocalNames = toLookup{ "(*temporary)", "(for generator)", "(for state)", "(for control)" } + +ModUtil.Metatables.Locals = { + __index = function( self, name ) + if excludedLocalNames[ name ] then return end + local level = getObjectData( self, "level" ) + local idx = 0 + repeat + idx = idx + 1 + local n, v = level.getlocal( level, idx ) + if n == name then + return v + end + until not n + end, + __newindex = function( self, name, value ) + if excludedLocalNames[ name ] then return end + local level = getObjectData( self, "level" ) + local idx = 0 + repeat + idx = idx + 1 + local n = level.getlocal( idx ) + if n == name then + level.setlocal( idx, value ) + return + end + until not n + end, + __len = function( ) + return 0 + end, + __next = function( self, name ) + local level = getObjectData( self, "level" ) + local idx = name and 0 or -1 + repeat + idx = idx + 1 + local n = level.getlocal( idx ) + if name and n == name or not name then + local value + repeat + idx = idx + 1 + n, value = level.getlocal( idx ) + if n and not excludedLocalNames[ n ] then + return n, value + end + until not n + end + until not n + end, + __inext = function( ) return end, + __pairs = function( self ) + return qrawpairs( self ), self + end, + __ipairs = function( self ) + return function( ) end, self + end +} + +ModUtil.Locals = ModUtil.Callable.Set( { }, function( _, level ) + return ModUtil.Proxy( { level = ModUtil.StackLevel( ( level or 1 ) + 1 ) }, ModUtil.Metatables.Locals ) +end ) -ModUtil.Metatables.LocalValues = { +ModUtil.Metatables.Locals.Values = { __index = function( self, idx ) - local name, value = rawget( self, "level" ).getlocal( idx ) + local name, value = getObjectData( self, "level" ).getlocal( idx ) if name then if not excludedLocalNames[ name ] then return value @@ -1508,7 +1537,7 @@ ModUtil.Metatables.LocalValues = { end end, __newindex = function( self, idx, value ) - local level = rawget( self, "level" ) + local level = getObjectData( self, "level" ) local name = level.getlocal( idx ) if name then if not excludedLocalNames[ name ] then @@ -1517,7 +1546,7 @@ ModUtil.Metatables.LocalValues = { end end, __len = function( self ) - local level = rawget( self, "level" ) + local level = getObjectData( self, "level" ) local idx = 1 while level.getlocal( level, idx ) do idx = idx + 1 @@ -1527,7 +1556,7 @@ ModUtil.Metatables.LocalValues = { __next = function( self, idx ) idx = idx or 0 idx = idx + 1 - local name, val = rawget( self, "level" ).getlocal( idx ) + local name, val = getObjectData( self, "level" ).getlocal( idx ) if name then if not excludedLocalNames[ name ] then return idx, val @@ -1538,25 +1567,22 @@ ModUtil.Metatables.LocalValues = { return qrawpairs( self ), self end, } -ModUtil.Metatables.LocalValues.__ipairs = ModUtil.Metatables.LocalValues.__pairs -ModUtil.Metatables.LocalValues.__inext = ModUtil.Metatables.LocalValues.__next +ModUtil.Metatables.Locals.Values.__ipairs = ModUtil.Metatables.Locals.Values.__pairs +ModUtil.Metatables.Locals.Values.__inext = ModUtil.Metatables.Locals.Values.__next --[[ Example Use: - for i, name, value in pairs( ModUtil.LocalValues( level ) ) do - -- + for i, value in pairs( ModUtil.Locals.Values( level ) ) do + -- stuff end -]] -function ModUtil.LocalValues( level ) - if level == nil then level = 1 end - local locals = { level = ModUtil.StackLevel( level + 1 ) } - setmetatable( locals, ModUtil.Metatables.LocalValues ) - return locals +--]] +function ModUtil.Locals.Values( level ) + return ModUtil.Proxy( { level = ModUtil.StackLevel( ( level or 1 ) + 1 ) }, ModUtil.Metatables.Locals.Values ) end -ModUtil.Metatables.LocalNames = { +ModUtil.Metatables.Locals.Names = { __index = function( self, idx ) - local name = rawget( self, "level" ).getlocal( idx ) + local name = getObjectData( self, "level" ).getlocal( idx ) if name then if not excludedLocalNames[ name ] then return name @@ -1564,11 +1590,11 @@ ModUtil.Metatables.LocalNames = { end end, __newindex = function( ) return end, - __len = ModUtil.Metatables.LocalValues.__len, + __len = ModUtil.Metatables.Locals.Values.__len, __next = function( self, idx ) if idx == nil then idx = 0 end idx = idx + 1 - local name = rawget( self, "level" ).getlocal( idx ) + local name = getObjectData( self, "level" ).getlocal( idx ) if name then if not excludedLocalNames[ name ] then return idx, name @@ -1579,89 +1605,23 @@ ModUtil.Metatables.LocalNames = { return qrawpairs( self ), self end, } -ModUtil.Metatables.LocalNames.__ipairs = ModUtil.Metatables.LocalNames.__pairs -ModUtil.Metatables.LocalNames.__inext = ModUtil.Metatables.LocalNames.__next +ModUtil.Metatables.Locals.Names.__ipairs = ModUtil.Metatables.Locals.Names.__pairs +ModUtil.Metatables.Locals.Names.__inext = ModUtil.Metatables.Locals.Names.__next --[[ Example Use: - for i, name, value in pairs( ModUtil.LocalNames( level ) ) do - -- - end -]] --- WORKS -function ModUtil.LocalNames( level ) - if level == nil then level = 1 end - local locals = { level = ModUtil.StackLevel( level + 1 ) } - setmetatable( locals, ModUtil.Metatables.LocalNames ) - return locals -end - -ModUtil.Metatables.Locals = { - __index = function( self, name ) - if excludedLocalNames[ name ] then return end - local level = rawget( self, "level" ) - local idx = 0 - repeat - idx = idx + 1 - local n, v = level.getlocal( level, idx ) - if n == name then - return v - end - until not n - end, - __newindex = function( self, name, value ) - if excludedLocalNames[ name ] then return end - local level = rawget( self, "level" ) - local idx = 0 - repeat - idx = idx + 1 - local n = level.getlocal( idx ) - if n == name then - level.setlocal( idx, value ) - return - end - until not n - end, - __len = function( ) - return 0 - end, - __next = function( self, name ) - local level = rawget( self, "level" ) - local idx = name and 0 or -1 - repeat - idx = idx + 1 - local n = level.getlocal( idx ) - if name and n == name or not name then - local value - repeat - idx = idx + 1 - n, value = level.getlocal( idx ) - if n and not excludedLocalNames[ n ] then - return n, value - end - until not n - end - until not n - end, - __inext = function( ) return end, - __pairs = function( self ) - return qrawpairs( self ), self - end, - __ipairs = function( self ) - return function( ) end, self + for i, name in pairs( ModUtil.Locals.Names( level ) ) do + -- stuff end -} - -function ModUtil.Locals( level ) - local locals = { level = ModUtil.StackLevel( ( level or 1 ) + 1 ) } - setmetatable( locals, ModUtil.Metatables.Locals ) - return locals +--]] +function ModUtil.Locals.Names( level ) + return ModUtil.Proxy( { level = ModUtil.StackLevel( ( level or 1 ) + 1 ) }, ModUtil.Metatables.Locals.Names ) end -ModUtil.Metatables.StackedLocals = { +ModUtil.Metatables.Locals.Stacked = { __index = function( self, name ) if excludedLocalNames[ name ] then return end - for _, level in pairs( rawget( self, "levels" ) ) do + for _, level in pairs( getObjectData( self, "levels" ) ) do local idx = 0 repeat idx = idx + 1 @@ -1674,7 +1634,7 @@ ModUtil.Metatables.StackedLocals = { end, __newindex = function( self, name, value ) if excludedLocalNames[ name ] then return end - for _, level in pairs( rawget( self, "levels" ) ) do + for _, level in pairs( getObjectData( self, "levels" ) ) do local idx = 0 repeat idx = idx + 1 @@ -1690,7 +1650,7 @@ ModUtil.Metatables.StackedLocals = { return 0 end, __next = function( self, name ) - local levels = rawget( self, "levels" ) + local levels = getObjectData( self, "levels" ) for _, level in pairs( levels ) do local idx = name and 0 or -1 repeat @@ -1714,7 +1674,7 @@ ModUtil.Metatables.StackedLocals = { return qrawpairs( self ), self end, __ipairs = function( self ) - return function() end, self + return function( ) end, self end } @@ -1724,808 +1684,414 @@ ModUtil.Metatables.StackedLocals = { be used. For example, if your function is called from CreateTraitRequirements, - you could access its 'local screen' as ModUtil.StackedLocals().screen - and its 'local hasRequirement' as ModUtil.StackedLocals().hasRequirement. -]] -function ModUtil.StackedLocals( level ) - local locals = { levels = ModUtil.StackLevels( ( level or 1 ) ) } - setmetatable( locals, ModUtil.Metatables.StackedLocals ) - return locals + you could access its 'local screen' as ModUtil.Locals.Stacked( ).screen + and its 'local hasRequirement' as ModUtil.Locals.Stacked( ).hasRequirement. +--]] +function ModUtil.Locals.Stacked( level ) + return ModUtil.Proxy( { levels = ModUtil.StackLevels( level or 1 ) }, ModUtil.Metatables.Locals.Stacked ) end --- Function Wrapping +-- Entangled Data Structures -ModUtil.Internal.WrapCallbacks = { } +ModUtil.Metatables.Entangled = { } ---[[ - Wrap a function, so that you can insert code that runs before/after that function - whenever it's called, and modify the return value if needed. +ModUtil.Metatables.Entangled.Union = { - Generally, you should use ModUtil.WrapBaseFunction instead for a more modder-friendly - interface. + __index = function( self, key ) + local value + for t in pairs( getObjectData( self, "Members" ) ) do + value = t[ key ] + if value ~= nil then + return value + end + end + return getObjectData( self, "Reserve" )[ key ] + end, + __newindex = function( self, key, value ) + if value ~= nil then + getObjectData( self, "Keys" )[ key ] = true + else + getObjectData( self, "Keys" )[ key ] = nil + end + local hit = false + for t in pairs( getObjectData( self, "Members" ) ) do + if t[ key ] ~= nil then + t[ key ] = value + hit = true + end + end + if hit then + getObjectData( self, "Reserve" )[ key ] = nil + else + getObjectData( self, "Reserve" )[ key ] = value + end + end, + __len = function( self ) + local m = #getObjectData( self, "Reserve" ) + local n + for t in pairs( getObjectData( self, "Members" ) ) do + n = #t + if n > m then + m = n + end + end + return m + end, + __next = function( self, key ) + key = next( getObjectData( self, "Keys" ), key ) + return key, self[ key ] + end, + __inext = function( self, idx ) + idx = ( idx or 0 ) + 1 + local val = self[ idx ] + if val == nil then return end + return idx, val + end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return qrawipairs( self ) + end - Multiple wrappers can be applied to the same function. +} - As an example, for WrapFunction( _G, { "UIFunctions", "OnButton1Pushed" }, wrapper, MyMod ) +ModUtil.Entangled.Union = ModUtil.Callable.Set( { }, function( _, ... ) + local keys, members = { }, toLookup{ ... } + local union = { Reserve = { }, Keys = keys, Members = members } + for t in pairs( members ) do + for k in pairs( t ) do + keys[ k ] = true + end + end + return ModUtil.Proxy( union, ModUtil.Metatables.Entangled.Union ) +end ) - Wrappers are stored in a structure like this: +function ModUtil.Entangled.Union.Add( union, ... ) + local members = getObjectData( union, "Members" ) + local keys = getObjectData( union, "Keys" ) + local reserve = getObjectData( union, "Reserve" ) + for t in pairs( toLookup{ ... } ) do + members[ t ] = true + for k in pairs( t ) do + keys[ k ] = true + reserve[ k ] = nil + end + end +end - ModUtil.Internal.WrapCallbacks[ _G ].UIFunctions.OnButton1Pushed = { - { id = 1, mod = MyMod, wrap=wrapper, func = } - } +function ModUtil.Entangled.Union.Sub( union, ... ) + local members = getObjectData( union, "Members" ) + local keys = getObjectData( union, "Keys" ) + for t in pairs( toLookup{ ... } ) do + members[ t ] = nil + end + for k in pairs( keys ) do + if union[ k ] == nil then + keys[ k ] = nil + end + end +end - If a second wrapper is applied via - WrapFunction( _G, { "UIFunctions", "OnButton1Pushed" }, wrapperFunction2, SomeOtherMod ) - then the resulting structure will be like: +ModUtil.Metatables.Entangled.Map = { - ModUtil.Internal.WrapCallbacks[ _G ].UIFunctions.OnButton1Pushed = { - { id = 1, mod = MyMod, wrap = wrapper, func = } - { id = 2, mod = SomeOtherMod, wrap = wrapper2, func = } - } + Data = { + __index = function( self, key ) + return getObjectData( self, "Map" )[ key ] + end, + __newindex = function( self, key, value ) + local data = getObjectData( self, "Map" ) + local prevValue = data[ key ] + data[ key ] = value + local preImage = getObjectData( self, "PreImage" ) + local prevKeys + if prevValue ~= nil then + prevKeys = preImage[ prevValue ] + end + local keys = preImage[ value ] + if not keys then + keys = { } + preImage[ value ] = keys + end + if prevKeys then + prevKeys[ key ] = nil + end + keys[ key ] = true + end, + __len = function( self ) + return #getObjectData( self, "Map" ) + end, + __next = function( self, key ) + return next( getObjectData( self, "Map" ), key ) + end, + __inext = function( self, idx ) + return inext( getObjectData( self, "Map" ), idx ) + end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return qrawipairs( self ) + end + }, - This allows several mods to apply wrappers to the same base function, and then: - - unwrap again later - - reapply the same wrappers to a new base function when it's overridden + PreImage = { + __index = function( self, value ) + return getObjectData( self, "PreImage" )[ value ] + end, + __newindex = function( self, value, keys ) + getObjectData( self, "PreImage" )[ value ] = keys + local data = getObjectData( self, "Map" ) + for key in pairs( data ) do + data[ key ] = nil + end + for key in ipairs( keys ) do + data[ key ] = value + end + end, + __len = function( self ) + return #getObjectData( self, "PreImage" ) + end, + __next = function( self, key ) + return next( getObjectData( self, "PreImage" ), key ) + end, + __inext = function( self, idx ) + return inext( getObjectData( self, "PreImage" ), idx ) + end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return qrawipairs( self ) + end + }, - This function also updates the entry in funcTable at indexArray to be the completely - wrapped function, ie. in our example with two wrappers it would do + Unique = { + + Data = { + __index = function( self, key ) + return getObjectData( self, "Data" )[ key ] + end, + __newindex = function( self, key, value ) + local data, inverse = getObjectData( self, "Data" ), getObjectData( self, "Inverse" ) + if value ~= nil then + local k = inverse[ value ] + if k ~= key then + if k ~= nil then + data[ k ] = nil + end + inverse[ value ] = key + end + end + if key ~= nil then + local v = data[ key ] + if v ~= value then + if v ~= nil then + inverse[ v ] = nil + end + data[ key ] = value + end + end + end, + __len = function( self ) + return #getObjectData( self, "Data" ) + end, + __next = function( self, key ) + return next( getObjectData( self, "Data" ), key ) + end, + __inext = function( self, idx ) + return inext( getObjectData( self, "Data" ), idx ) + end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return qrawipairs( self ) + end + }, + + Inverse = { + __index = function( self, value ) + return getObjectData( self, "Inverse" )[ value ] + end, + __newindex = function( self, value, key ) + local data, inverse = getObjectData( self, "Data" ), getObjectData( self, "Inverse" ) + if value ~= nil then + local k = inverse[ value ] + if k ~= key then + if k ~= nil then + data[ k ] = nil + end + inverse[ value ] = key + end + end + if key ~= nil then + local v = data[ key ] + if v ~= value then + if v ~= nil then + inverse[ v ] = nil + end + data[ key ] = value + end + end + end, + __len = function( self ) + return #getObjectData( self, "Inverse" ) + end, + __next = function( self, value ) + return next( getObjectData( self, "Inverse" ), value ) + end, + __inext = function( self, idx ) + return inext( getObjectData( self, "Inverse" ), idx ) + end, + __pairs = function( self ) + return qrawpairs( self ) + end, + __ipairs = function( self ) + return qrawipairs( self ) + end + } - UIFunctions.OnButton1Pushed = + } - funcTable - the table the function is stored in (usually _G) - indexArray - the array of path elements to the function in the table - wrapFunc - the wrapping function - mod - (optional) the mod installing the wrapper, for informational purposes -]] -function ModUtil.WrapFunction( funcTable, indexArray, wrapFunc, mod ) - if type( wrapFunc ) ~= "function" then return end - if not funcTable then return end - local func = ModUtil.SafeGet( funcTable, indexArray ) - if type( func ) ~= "function" then return end - - ModUtil.NewTable( ModUtil.Internal.WrapCallbacks, funcTable ) - local tempTable = ModUtil.SafeGet( ModUtil.Internal.WrapCallbacks[ funcTable ], indexArray ) - if tempTable == nil then - tempTable = { } - ModUtil.SafeSet( ModUtil.Internal.WrapCallbacks[ funcTable ], indexArray, tempTable ) - end - table.insert( tempTable, { Id = #tempTable + 1, Mod = mod, Wrap = wrapFunc, Func = func } ) - - ModUtil.SafeSet( skipenv( funcTable ), indexArray, function( ... ) - return wrapFunc( func, ... ) - end ) -end +} ---[[ - Internal utility that reapplies the list of wrappers when the base function changes. +ModUtil.Entangled.Map = ModUtil.Callable.Set( { Unique = { } }, function( ) + local data, preImage = { }, { } + data, preImage = { Data = data, PreImage = preImage }, { Data = data, PreImage = preImage } + data = ModUtil.Proxy( data, ModUtil.Metatables.Entangled.Map.Data ) + preImage = ModUtil.Proxy( preImage, ModUtil.Metatables.Entangled.Map.PreImage ) + return { Data = data, Index = preImage, PreImage = preImage } +end ) - For example. if the list of wrappers looks like: - ModUtil.Internal.WrapCallbacks[ _G ].UIFunctions.OnButton1Pushed = { - { id = 1, mod = MyMod, wrap = wrapper, func = } - { id = 2, mod = SomeOtherMod, wrap = wrapper2, func = } - { id = 3, mod = ModNumber3, wrap = wrapper3, func = } - } +ModUtil.Entangled.Map.Unique = ModUtil.Callable.Set( { }, function( ) + local data, inverse = { }, { } + data, inverse = { Data = data, Inverse = inverse }, { Data = data, Inverse = inverse } + data = ModUtil.Proxy( data, ModUtil.Metatables.Entangled.Map.Unique.Data ) + inverse = ModUtil.Proxy( inverse, ModUtil.Metatables.Entangled.Map.Unique.Inverse ) + return { Data = data, Index = inverse, Inverse = inverse } +end ) - and the base function is modified by setting [ 1 ].func to a new value, like so: - ModUtil.Internal.WrapCallbacks[ _G ].UIFunctions.OnButton1Pushed = { - { id = 1, mod = MyMod, wrap = wrapper, func = } - { id = 2, mod = SomeOtherMod, wrap = wrapper2, func = } - { id = 3, mod = ModNumber3, wrap = wrapper3, func = } - } +-- Context Managers - Then rewrap function will fix up eg. [ 2 ].func, [ 3 ].func so that the correct wrappers are applied - ModUtil.Internal.WrapCallbacks[ _G ].UIFunctions.OnButton1Pushed = { - { id = 1, mod = MyMod, wrap = wrapper, func = } - { id = 2, mod = SomeOtherMod, wrap = wrapper2, func = } - { id = 3, mod = ModNumber3, wrap = wrapper3, func = } - } - and also update the entry in funcTable to be the completely wrapped function, ie. +local threadContexts = { } +setmetatable( threadContexts, { __mode = "kv" } ) - UIFunctions.OnButton1Pushed = - - funcTable - the table the function is stored in (usually _G) - indexArray - the array of path elements to the function in the table -]] -function ModUtil.RewrapFunction( funcTable, indexArray ) - local wrapCallbacks = ModUtil.SafeGet( ModUtil.Internal.WrapCallbacks[ funcTable ], indexArray ) - local preFunc = nil - - for _, tempTable in ipairs( wrapCallbacks ) do - if preFunc then - tempTable.Func = preFunc - end - preFunc = function( ... ) - return tempTable.Wrap( tempTable.Func, ... ) - end - ModUtil.SafeSet( skipenv( funcTable ), indexArray, preFunc ) - end - -end - ---[[ - Removes the most recent wrapper from a function, and restore it to its - previous value. - - Generally, you should use ModUtil.UnwrapBaseFunction instead for a more - modder-friendly interface. - - funcTable - the table the function is stored in (usually _G) - indexArray - the array of path elements to the function in the table -]] -function ModUtil.UnwrapFunction( funcTable, indexArray ) - if not funcTable then return end - local func = ModUtil.SafeGet( skipenv( funcTable ), indexArray ) - if type( func ) ~= "function" then return end - - local tempTable = ModUtil.SafeGet( ModUtil.Internal.WrapCallbacks[ funcTable ], indexArray ) - if not tempTable then return end - local funcData = table.remove( tempTable ) -- removes the last value - if not funcData then return end - - ModUtil.SafeSet( skipenv( funcTable ), indexArray, funcData.Func ) - return funcData -end - - ---[[ - Wraps the function with the path given by baseFuncPath, so that you - can execute code before or after the original function is called, - or modify the return value. - - For example: - - ModUtil.WrapBaseFunction( "CreateNewHero", function( baseFunc, prevRun, args ) - local hero = baseFunc( prevRun, args ) - hero.Health = 1 - return hero - end, YourMod ) - - will cause the function CreateNewHero to be wrapped so that the hero's - health is set to 1 before the hero is returned. - - This provides better compatibility with other mods that overriding the - function, since multiple mods can wrap the same function. - - baseFuncPath - the (global) path to the function, as a string - for most SGG-provided functions, this is just the function's name - eg. "CreateRoomReward" or "SetTraitsOnLoot" - wrapFunc - the function to wrap around the base function - this function receives the base function as its first parameter. - all subsequent parameters should be the same as the base function - mod - (optional) the object for your mod, for debug purposes -]] -function ModUtil.WrapBaseFunction( baseFuncPath, wrapFunc, mod ) - local pathArray = ModUtil.PathToIndexArray( baseFuncPath ) - ModUtil.WrapFunction( _G, pathArray, wrapFunc, mod ) -end - ---[[ - Internal function that reapplies all the wrappers to a function. -]] -function ModUtil.RewrapBaseFunction( baseFuncPath ) - local pathArray = ModUtil.PathToIndexArray( baseFuncPath ) - ModUtil.RewrapFunction( _G, pathArray ) -end +ModUtil.Metatables.Context = { + __call = function( self, arg, callContext, ... ) ---[[ - Remove the most recent wrapper from the function at baseFuncPath, - restoring it to its previous state + local thread = coroutine.running( ) - Note that this does _not_ remove overrides, and it removes the most - recent wrapper regardless of which mod added it, so be careful! + local contextInfo = { + call = callContext, + wrap = function( ... ) callContext( ... ) end, + parent = threadContexts[ thread ], + thread = thread + } - baseFuncPath - the (global) path to the function, as a string - for most SGG-provided functions, this is just the function's name - eg. "CreateRoomReward" or "SetTraitsOnLoot" -]] -function ModUtil.UnwrapBaseFunction( baseFuncPath ) - local pathArray = ModUtil.PathToIndexArray( baseFuncPath ) - ModUtil.UnwrapFunction( _G, pathArray ) -end + threadContexts[ thread ] = contextInfo --- Override Management + local penv = threadEnvironments[ thread ] + contextInfo.penv = threadEnvironments[ thread ] or _G -ModUtil.Internal.Overrides = { } + contextInfo.context = self + contextInfo.args = table.pack( ... ) + contextInfo.arg = arg + contextInfo.data = { } + contextInfo.params = table.pack( getObjectData( self, "prepContext" )( contextInfo ) ) + + threadEnvironments[ thread ] = contextInfo.env + contextInfo.response = table.pack( contextInfo.wrap( table.unpack( contextInfo.params ) ) ) + threadEnvironments[ thread ] = penv -local function getBaseValueForWraps( baseTable, indexArray ) - local baseValue = ModUtil.SafeGet( skipenv( baseTable ), indexArray ) - local wrapCallbacks = nil - wrapCallbacks = ModUtil.SafeGet( ModUtil.Internal.WrapCallbacks[ baseTable ], indexArray ) - if wrapCallbacks then - if wrapCallbacks[ 1 ] then - baseValue = wrapCallbacks[ 1 ].Func + if getObjectData( self, "postCall" ) then + contextInfo.final = table.pack( getObjectData( self, "postCall" )( contextInfo ) ) end - end - - return baseValue -end - -local function setBaseValueForWraps( baseTable, indexArray, value ) - local baseValue = ModUtil.SafeGet( skipenv( baseTable ), indexArray ) - - if type( baseValue ) ~= "function" or type( value ) ~= "function" then return false end - - local wrapCallbacks = nil - wrapCallbacks = ModUtil.SafeGet( ModUtil.Internal.WrapCallbacks[ baseTable ], indexArray ) - if wrapCallbacks then if wrapCallbacks[ 1 ] then - baseValue = wrapCallbacks[ 1 ].Func - wrapCallbacks[ 1 ].Func = value - ModUtil.RewrapFunction( baseTable, indexArray ) - return true - end end - - return false -end - ---[[ - Override a value in baseTable. - - Generally, you should use ModUtil.BaseOverride instead for a more modder-friendly - interface. - - If the value is a function, overrides only the base function, - preserving all the wraps added with ModUtil.WrapFunction. - - The previous value is stored so that it can be restored later if desired. - For example, ModUtil.Override( _G, [ "UIFunctions", "OnButton1" ], overrideFunc, MyMod ) - will result in a data structure like: - - ModUtil.Internal.Overrides[ _G ].UIFunctions.OnButton1 = { - { id = 1, mod = MyMod, value = overrideFunc, base = } - } - and subsequent overides wil be added as subsequent entries in the same table. + threadContexts[ thread ] = contextInfo.parent - baseTable - the table in which to override, usually _G (globals) - indexArray - the list of indices - value - the new value - mod - (optional) the mod that performed the override, for debugging purposes -]] -function ModUtil.Override( baseTable, indexArray, value, mod ) - if not baseTable then return end - - local baseValue = getBaseValueForWraps( baseTable, indexArray ) - - ModUtil.NewTable( ModUtil.Internal.Overrides, baseTable ) - local tempTable = ModUtil.SafeGet( ModUtil.Internal.Overrides[ baseTable ], indexArray ) - if tempTable == nil then - tempTable = { } - ModUtil.SafeSet( ModUtil.Internal.Overrides[ baseTable ], indexArray, tempTable ) - end - table.insert( tempTable, { Id = #tempTable + 1, Mod = mod, Value = value, Base = baseValue } ) - - if not setBaseValueForWraps( baseTable, indexArray, value ) then - ModUtil.SafeSet( skipenv( baseTable ), indexArray, value ) - end -end - ---[[ - Undo the most recent override performed with ModUtil.Override, restoring - the previous value. - - Generally, you should use ModUtil.BaseRestore instead for a more modder-friendly - interface. - - If the previous value is a function, the current stack of wraps for that - IndexArray will be reapplied to it. - - baseTable = the table in which to undo the override, usually _G (globals) - indexArray - the list of indices -]] -function ModUtil.Restore( baseTable, indexArray ) - if not baseTable then return end - local tempTable = ModUtil.SafeGet( ModUtil.Internal.Overrides[ baseTable ], indexArray ) - if not tempTable then return end - local baseData = table.remove( tempTable ) -- remove the last entry - if not baseData then return end - - if not setBaseValueForWraps( baseTable, indexArray, baseData.Base ) then - ModUtil.SafeSet( skipenv( baseTable ), indexArray, baseData.Base ) - end - return baseData -end - ---[[ - Override the global value at the given basePath. - - If the Value is a function, preserves the wraps - applied with ModUtil.WrapBaseFunction et. al. - - basePath - the path to override, as a string - value - the new value to store at the path - mod - (optional) the mod performing the override, - for debug purposes -]] -function ModUtil.BaseOverride( basePath, value, mod ) - ModUtil.Override( _G, ModUtil.PathToIndexArray( basePath ), value, mod ) -end - ---[[ - Undo the most recent override performed with ModUtil.Override, - or ModUtil.BaseOverride, restoring the previous value. - - Use this carefully - if you are not the most recent mod to - override the given path, the results may be unexpected. - - basePath - the path to restore, as a string -]] -function ModUtil.BaseRestore( basePath ) - ModUtil.Restore( _G, ModUtil.PathToIndexArray( basePath ) ) -end - --- Wrap/Override interaction - -function ModUtil.GetOriginalValue( baseTable, indexArray ) - local baseValue = ModUtil.SafeGet( ModUtil.Internal.Overrides[ baseTable ], indexArray ) - if baseValue then - baseValue = baseValue[ #baseValue ].Base - else - baseValue = ModUtil.SafeGet( ModUtil.Internal.WrapCallbacks[ baseTable ], indexArray ) - if baseValue then - baseValue = baseValue[ 1 ].Func - else - baseValue = ModUtil.SafeGet( baseTable, indexArray ) + if contextInfo.final then + return table.unpack( contextInfo.final ) end end - return baseValue -end - -function ModUtil.GetOriginalBaseValue( basePath ) - return ModUtil.GetOriginalValue( _G, ModUtil.PathToIndexArray( basePath ) ) -end - --- Automatically updating data structures - -ModUtil.Metatables.EntangledInvertibleTable = { - __index = function( self, key ) - return rawget( self, "Table" )[ key ] - end, - __newindex = function( self, key, value ) - local Table, Index = rawget( self, "Table" ), rawget( self, "Index" ) - if value ~= nil then - local k = Index[ value ] - if k ~= key then - if k ~= nil then - Table[ k ] = nil - end - Index[ value ] = key - end - end - if key ~= nil then - local v = Table[ key ] - if v ~= value then - if v ~= nil then - Index[ v ] = nil - end - Table[ key ] = value - end - end - end, - __len = function( self ) - return #rawget( self, "Table" ) - end, - __next = function( self, key ) - return next( rawget( self, "Table" ), key ) - end, - __inext = function( self, idx ) - return inext( rawget( self, "Table" ), idx ) - end, - __pairs = function( self ) - return qrawpairs( self ) - end, - __ipairs = function( self ) - return qrawipairs( self ) - end } -ModUtil.Metatables.EntangledInvertibleIndex = { - __index = function( self, value ) - return rawget( self, "Index" )[ value ] - end, - __newindex = function( self, value, key ) - local table, index = rawget( self, "Table" ), rawget( self, "Index" ) - if value ~= nil then - local k = index[ value ] - if k ~= key then - if k ~= nil then - table[ k ] = nil - end - index[ value ] = key - end - end - if key ~= nil then - local v = table[ key ] - if v ~= value then - if v ~= nil then - index[ v ] = nil - end - table[ key ] = value - end - end - end, - __len = function( self ) - return #rawget( self, "Index" ) - end, - __next = function( self, value ) - return next( rawget( self, "Index" ), value ) - end, - __inext = function( self, idx ) - return inext( rawget( self, "Index" ), idx ) - end, - __pairs = function( self ) - return qrawpairs( self ) - end, - __ipairs = function( self ) - return qrawipairs( self ) - end -} - -function ModUtil.New.EntangledInvertiblePair( ) - local table, index = { }, { } - table, index = { Table = table, Index = index }, { Table = table, Index = index } - setmetatable( table, ModUtil.Metatables.EntangledInvertibleTable ) - setmetatable( index, ModUtil.Metatables.EntangledInvertibleIndex ) - return { Table = table, Index = index } -end - -function ModUtil.New.EntangledInvertiblePairFromTable( tableArg ) - local pair = ModUtil.New.EntangledInvertiblePair( ) - for key, value in pairs( tableArg ) do - pair.Table[ key ] = value - end - return pair -end +ModUtil.Context = ModUtil.Callable.Set( { }, function( _, prepContext, postCall ) + return ModUtil.Proxy( { prepContext = prepContext, postCall = postCall }, ModUtil.Metatables.Context ) +end ) -function ModUtil.New.EntangledInvertiblePairFromIndex( index ) - local pair = ModUtil.New.EntangledInvertiblePair( ) - for value, key in pairs( index ) do - pair.Index[ value ] = key - end - return pair -end +ModUtil.Context.Data = ModUtil.Context( function( info ) + local tbl = info.arg + info.env = setmetatable( { }, { + __index = function( _, key ) return tbl[ key ] or info.penv[ key ] end, + __newindex = tbl + } ) +end ) -ModUtil.Metatables.EntangledMap = { - __index = function( self, key ) - return rawget( self, "Map" )[ key ] - end, - __newindex = function( self, key, value ) - local map = rawget( self, "Map" ) - local prevValue = map[ key ] - map[ key ] = value - local preImage = rawget( self, "PreImage" ) - local prevKeys - if prevValue ~= nil then - prevKeys = preImage[ prevValue ] - end - local keys = preImage[ value ] - if not keys then - keys = { } - preImage[ value ] = keys - end - if prevKeys then - prevKeys[ key ] = nil - end - keys[ key ] = true - end, - __len = function( self ) - return #rawget( self, "Map" ) - end, - __next = function( self, key ) - return next( rawget( self, "Map" ), key ) - end, - __inext = function( self, idx ) - return inext( rawget( self, "Map" ), idx ) - end, - __pairs = function( self ) - return qrawpairs( self ) - end, - __ipairs = function( self ) - return qrawipairs( self ) - end -} +ModUtil.Context.Meta = ModUtil.Context( function( info ) + local tbl = ModUtil.Node.Data.Metatable.New( info.arg ) + info.env = setmetatable( { }, { + __index = function( _, key ) return tbl[ key ] or info.penv[ key ] end, + __newindex = tbl + } ) +end ) -ModUtil.Metatables.EntangledPreImage = { - __index = function( self, value ) - return rawget( self, "PreImage" )[ value ] - end, - __newindex = function( self, value, keys ) - rawget( self, "PreImage" )[ value ] = keys - local map = rawget( self, "Map" ) - for key in pairs( map ) do - map[ key ] = nil - end - for key in ipairs( keys ) do - map[ key ] = value - end - end, - __len = function( self ) - return #rawget( self, "PreImage" ) +ModUtil.Context.Call = ModUtil.Context( + function( info ) + info.env = setmetatable( { }, { __index = info.penv } ) end, - __next = function( self, key ) - return next( rawget( self, "PreImage" ), key ) - end, - __inext = function( self, idx ) - return inext( rawget( self, "PreImage" ), idx ) - end, - __pairs = function( self ) - return qrawpairs( self ) - end, - __ipairs = function( self ) - return qrawipairs( self ) + function ( info ) + local thread = coroutine.running( ) + local penv = threadEnvironments[ thread ] + threadEnvironments[ thread ] = info.env + local ret = table.pack( info.arg( table.unpack( info.args ) ) ) + threadEnvironments[ thread ] = penv + return table.unpack( ret ) end -} - -function ModUtil.New.EntangledPair( ) - local map, preImage = { }, { } - map, preImage = { Map = map, PreImage = preImage }, { Map = map, PreImage = preImage } - setmetatable( map, ModUtil.Metatables.EntangledMap ) - setmetatable( preImage, ModUtil.Metatables.EntangledPreImage ) - return { Map = map, PreImage = preImage } -end +) -function ModUtil.New.EntangledPairFromTable( tableArg ) - local pair = ModUtil.New.EntangledPair( ) - for key, value in pairs( tableArg ) do - pair.Map[ key ] = value - end - return pair -end - -function ModUtil.New.EntangledPairFromPreImage( preImage ) - local pair = ModUtil.New.EntangledPair( ) - for value, keys in pairs( preImage ) do - pair.PreImage[ value ] = keys - end - return pair -end +ModUtil.Context.Wrap = ModUtil.Callable.Set( { }, function( _, func, context, mod ) + return ModUtil.Wrap( func, function( base, ... ) ModUtil.Context.Call( base, context, ... ) end, mod ) +end ) -ModUtil.Metatables.EntangledQueueData = { - __index = function( self, key ) - return rawget( self, "Data" )[ key ] - end, - __newindex = function( self, key, value ) - local data = rawget( self, "Data" ) - local prevValue = data[ key ] - data[ key ] = value - local order = rawget( self, "Order" ) - local prevOrder = nil - local prevOrderPair - if prevValue ~= nil then - prevOrderPair = order[ prevValue ] - if not prevOrderPair then - prevOrderPair = ModUtil.New.EntangledInvertiblePair( ) - order[ prevValue ] = prevOrderPair - end - prevOrder = prevOrderPair.Index[ key ] - end - local orderPair = order[ value ] - if not orderPair then - orderPair = ModUtil.New.EntangledInvertiblePair( ) - order[ value ] = orderPair - end - if prevOrder then - table.remove( prevOrderPair.Table, prevOrder ) - end - table.insert( orderPair.Table, key ) - end, - __len = function( self ) - return #rawget( self, "Data" ) - end, - __next = function( self, key ) - return next( rawget( self, "Data" ), key ) - end, - __inext = function( self, idx ) - return inext( rawget( self, "Data" ), idx ) - end, - __pairs = function( self ) - return qrawpairs( self ) - end, - __ipairs = function( self ) - return qrawipairs( self ) - end -} -ModUtil.Metatables.EntangledQueueOrder = { - __index = function( self, value ) - return rawget( self, "Order" )[ value ] - end, - __newindex = function( self, value, pair ) - rawget( self, "Order" )[ value ] = pair - local data = rawget( self, "Data" ) - for _, key in pairs( data ) do - data[ key ] = nil - end - for _, key in ipairs( pair.Table ) do - data[ key ] = value - end - end, - __len = function( self ) - return #rawget( self, "Order" ) - end, - __next = function( self, key ) - return next( rawget( self, "Order" ), key ) +ModUtil.Context.Wrap.Static = ModUtil.Context( + function( info ) + info.env = setmetatable( { }, { __index = info.penv } ) end, - __inext = function( self, idx ) - return inext( rawget( self, "Order" ), idx ) - end, - __pairs = function( self ) - return qrawpairs( self ) - end, - __ipairs = function( self ) - return qrawipairs( self ) + function ( info ) + return ModUtil.Wrap( info.arg, function( base, ... ) + local thread = coroutine.running( ) + local penv = threadEnvironments[ thread ] + threadEnvironments[ thread ] = info.env + local ret = table.pack( base( ... ) ) + threadEnvironments[ thread ] = penv + return table.unpack( ret ) + end, info.args[1] ) end -} +) -function ModUtil.New.EntangledQueuePair( ) - local data, order = { }, { } - data, order = { Data = data, Order = order }, { Data = data, Order = order } - setmetatable( data, ModUtil.Metatables.EntangledQueueData ) - setmetatable( order, ModUtil.Metatables.EntangledQueueOrder ) - return { Data = data, Order = order } -end +-- Special Traversal Nodes -function ModUtil.New.EntangledQueuePairFromData( data ) - local pair = ModUtil.New.EntangledQueuePair( ) - for key, value in pairs( data ) do - pair.Data[ key ] = value - end - return pair -end +ModUtil.Node = ModUtil.Entangled.Map.Unique( ) -function ModUtil.New.EntangledQueuePairFromOrder( order ) - local pair = ModUtil.New.EntangledQueuePair( ) - for value, keys in pairs( order ) do - pair.Order[ value ] = keys +function ModUtil.Node.New( parent, key ) + local nodeType = ModUtil.Node.Inverse[ key ] + if nodeType then + return ModUtil.Node.Data[ nodeType ].New( parent ) end - return pair -end - --- Context Managers (EXPERIMENTAL) (WIP) (BROKEN) (INCOMPLETE) - -ModUtil.Context = { } - -ModUtil.Metatables.Environment = { - __index = function( self, key ) - if key == "_G" then - return rawget( self, "global" ) - end - local value = rawget( self, "data" )[ key ] - if value ~= nil then - return value - end - return ( rawget( self, "fallback" ) or { } )[ key ] - end, - __newindex = function( self, key, value ) - rawget( self, "data" )[ key ] = value - if key == "_G" then - rawset( self, "global", value ) - end - end, - __len = function( self ) - return #rawget( self, "data" ) - end, - __next = function( self, key ) - local out, first = { key }, true - while out[ 2 ] == nil and ( out[ 1 ] ~= nil or first ) do - first = false - out = { next( rawget( self, "data" ), out[ 1 ] ) } - if out[ 1 ] == "_G" then - out = { "_G", rawget( self, "global" ) } - end - end - return table.unpack( out ) - end, - __inext = function( self, idx ) - local out, first = { idx }, true - while out[ 2 ] == nil and ( out[ 1 ] ~= nil or first ) do - first = false - out = { inext( rawget( self, "data" ), out[ 1 ] ) } - if out[ 1 ] == "_G" then - out = { "_G", rawget( self, "global" ) } - end - end - return table.unpack( out ) - end, - __pairs = function( self ) - return qrawpairs( self ) - end, - __ipairs = function( self ) - return qrawipairs( self ) + local tbl = parent[ key ] + if tbl == nil then + tbl = { } + parent[ key ] = tbl end -} - -ModUtil.Metatables.Context = { - __call = function( self, targetPath_or_targetIndexArray, callContext, ... ) - local oldContextInfo = ModUtil.StackedLocals( 2 )._ContextInfo - local contextInfo = { - call = callContext, - parent = oldContextInfo - } - - contextInfo.targetIndexArray = ModUtil.PathToIndexArray( targetPath_or_targetIndexArray ) or { } - if oldContextInfo ~= nil then - contextInfo.indexArray = ShallowCopyTable( oldContextInfo.indexArray ) - contextInfo.baseTable = oldContextInfo.baseTable - else - contextInfo.indexArray = { } - contextInfo.baseTable = _G - end - - local callContextProcessor = rawget( self, "callContextProcessor" ) - contextInfo.callContextProcessor = callContextProcessor - - local contextData, contextArgs = callContextProcessor( contextInfo, ... ) - contextData = contextData or _G - contextArgs = contextArgs or { } - - local _ContextInfo = contextInfo - _ContextInfo.data = contextData - _ContextInfo.args = contextArgs - - local callWrap = function( ... ) callContext( ... ) end - surrogateEnvironments[ callWrap ] = contextData - callWrap( table.unpack( contextArgs ) ) + if type( tbl ) ~= "table" then + error( "key already occupied by a non-table", 2 ) end -} - -function ModUtil.New.Context( callContextProcessor ) - local context = { callContextProcessor = callContextProcessor } - setmetatable( context, ModUtil.Metatables.Context ) - return context + return tbl end -ModUtil.Context.Call = ModUtil.New.Context( function( info ) - info.indexArray = ModUtil.JoinIndexArrays( info.indexArray, info.targetIndexArray ) - local obj = ModUtil.SafeGet( info.baseTable, info.indexArray ) - while type( obj ) == "table" do - local meta = getmetatable( obj ) - if meta then - if meta.__call then - table.insert( info.indexArray, ModUtil.Nodes.Table.Metatable ) - table.insert( info.indexArray, "__call" ) - obj = meta.__call - end - end - end - local env = { data = ModUtil.NewTable( obj, ModUtil.Nodes.Table.Environment ), fallback = _G } - env.global = env - setmetatable( env, ModUtil.Metatables.Environment ) - return env -end ) - -ModUtil.Context.Meta = ModUtil.New.Context( function( info ) - info.indexArray = ModUtil.JoinIndexArrays( info.indexArray, info.targetIndexArray ) - local parent = ModUtil.SafeGet( info.baseTable, info.indexArray ) - if parent == nil then - parent = { } - ModUtil.SafeSet( info.baseTable, info.indexArray, parent ) - end - local env = { data = ModUtil.NewTable( parent, ModUtil.Nodes.Table.Metatable ), fallback = _G } - env.global = env - setmetatable( env, ModUtil.Metatables.Environment ) - return env -end ) - -ModUtil.Context.Data = ModUtil.New.Context( function( info ) - info.indexArray = ModUtil.JoinIndexArrays( info.indexArray, info.targetIndexArray ) - local tempArray = ShallowCopyTable( info.indexArray ) - local key = table.remove( tempArray ) - local parent = ModUtil.SafeGet( info.baseTable, tempArray ) - if parent == nil then - parent = { } - ModUtil.SafeSet( info.baseTable, tempArray, parent ) - end - local env = { data = ModUtil.NewTable( parent, key ), fallback = _G } - env.global = env - setmetatable( env, ModUtil.Metatables.Environment ) - return env -end ) - --- Special traversal nodes (EXPERIMENTAL) (WIP) (INCOMPLETE) - -ModUtil.Nodes = ModUtil.New.EntangledInvertiblePair( ) - -ModUtil.Nodes.Table.Metatable = { +ModUtil.Node.Data.Meta = { New = function( obj ) local meta = getmetatable( obj ) if meta == nil then @@ -2543,370 +2109,197 @@ ModUtil.Nodes.Table.Metatable = { end } -ModUtil.Nodes.Table.Environment = { +ModUtil.Node.Data.Call = { New = function( obj ) - local env = surrogateEnvironments[ obj ] - if env == nil or getmetatable( env ) ~= ModUtil.Metatables.Environment then - env = { } - env.data = env.data or { } - env.fallback = _G - env.global = env - setmetatable( env, ModUtil.Metatables.Environment ) - surrogateEnvironments[ obj ] = env - end - return env + return ModUtil.Callable.Func.Get( obj ) or error( "node new rejected, new call nodes are not meaningfully mutable.", 2 ) end, Get = function( obj ) - return surrogateEnvironments[ obj ] + return ModUtil.Callable.Func.Get( obj ) end, Set = function( obj, value ) - surrogateEnvironments[ obj ] = value + ModUtil.Callable.Set( ModUtil.Callable.Get( obj ), value ) return true end } --- Identifier system (EXPERIMENTAL) +ModUtil.Node.Data.UpValues = { + New = function( obj ) + return ModUtil.UpValues( obj ) + end, + Get = function( obj ) + return ModUtil.UpValues( obj ) + end, + Set = function( ) + error( "node set rejected, upvalues node cannot be set.", 2 ) + end +} -ModUtil.Identifiers = ModUtil.New.EntangledInvertiblePair( ) -ModUtil.Identifiers.Index._G = _G -ModUtil.Identifiers.Index.ModUtil = ModUtil +-- Identifier System -ModUtil.Mods = ModUtil.New.EntangledInvertiblePair( ) -ModUtil.Mods.Table.ModUtil = ModUtil +ModUtil.Identifiers = ModUtil.Entangled.Map.Unique( ) +setmetatable( getObjectData( ModUtil.Identifiers.Data, "Inverse" ), { __mode = "k" } ) +setmetatable( getObjectData( ModUtil.Identifiers.Inverse, "Data" ), { __mode = "v" } ) --- Mods tracking (EXPERIMENTAL) (WIP) (UNTESTED) (INCOMPLETE) +ModUtil.Identifiers.Inverse._G = _G +ModUtil.Identifiers.Inverse.ModUtil = ModUtil ---[[ - Users should only ever opt-in to running this function -]] -function ModUtil.EnableModHistory( ) - if not ModHistory then - ModHistory = { } - if PersistVariable then PersistVariable{ Name = "ModHistory" } end - SaveIgnores[ "ModHistory" ] = nil - end -end +ModUtil.Mods = ModUtil.Entangled.Map.Unique( ) +setmetatable( getObjectData( ModUtil.Mods.Data, "Inverse" ), { __mode = "k" } ) +setmetatable( getObjectData( ModUtil.Mods.Inverse, "Data" ), { __mode = "v" } ) +ModUtil.Mods.Data.ModUtil = ModUtil -function ModUtil.DisableModHistory( ) - if not ModHistory then - ModHistory = { } - if PersistVariable then PersistVariable{ Name = "ModHistory" } end - SaveIgnores[ "ModHistory" ] = nil - end -end +-- Function Wrapping, Decoration, Overriding, Referral -function ModUtil.UpdateModHistoryEntry( options ) - if options.Override then - ModUtil.EnableModHistory( ) - end - if ModHistory then - local mod = options.Mod - local path = options.Path - if mod == nil then - mod = ModUtil.Mods.Table[ path ] - end - if path == nil then - path = ModUtil.Mods.Index[ mod ] - end - local entry = ModHistory[ path ] - if entry == nil then - entry = { } - ModHistory[ path ] = entry - end - if options.Version then - entry.Version = options.Version - end - if options.FirstTime or options.All then - if not entry.FirstTime then - entry.FirstTime = os.time( ) - end - end - if options.LastTime or options.All then - entry.LastTime = os.time( ) - end +local decorators = setmetatable( { }, { __mode = "k" } ) +local overrides = setmetatable( { }, { __mode = "k" } ) - if options.Count or options.All then - local count = entry.Count - if not count then - count = 0 - end - entry.Count = count + 1 - end +local function wrapDecorator( wrap ) + return function( base ) + return function( ... ) return wrap( base, ... ) end end end -function ModUtil.PopulateModHistory( options ) - if not options then - options = { } - end - for path, mod in pairs( ModUtil.Mods.Table ) do - options.Path, options.Mod = path, mod - ModUtil.UpdateModHistoryEntry( options ) +ModUtil.Decorate = ModUtil.Callable.Set( { }, function( _, base, func, mod ) + local out = func( base ) + if decorators[ out ] then + error( "decorator produced duplicate reference", 2 ) end -end - --- Wrap/Overrides within functions (EXPERIMENTAL) (LIKELY DEPRECATED) - -ModUtil.Internal.PerFunctionEnv = { } - ---[[ - Create a new table whose getters and setters will default to the given - baseTable, except in cases where values are setOverride into the overrideTable. - This allows pinpoint overriding of values in the override table, while allowing - all other accesses to continue to operate as if they were operating on baseTable. - The overrides are stored in the _Overrides subtable. For example: - _Overrides = { - CurrentRun = { - CurrentRoom = { - _IsModUtilOverride = true, - _Value = { - Name = "RoomSimple01", - ... - } - } - } - } - would be the result of overriding "CurrentRun.CurrentRoom" with a table represnting a room. - Any accesses that happen above the override point (ie. reads/writes to CurrentRun) are - reed to the base table. Any accesses that happen at or below the override point (ie. - reads / writes to CurrentRun.CurrentRoom or CurrentRun.CurrentRoom.Name) are intercepted - and apply to the overridden value instead. - baseTable - the table to access for entries not specifically overridden -]] -local function makeOverrideTable( baseTable, overrides ) - local overrideTable = { - _IsModUtilOverrideTable = true, - _Overrides = overrides or { }, - _BaseTable = baseTable - } - setmetatable( overrideTable, ModUtil.Metatables.OverrideTable ) - return overrideTable -end - -ModUtil.Metatables.OverrideTable = { - __index = function( self, name ) - local baseResult = self._BaseTable[ name ] - local overridesResult = self._Overrides[ name ] - if overridesResult == nil then - return baseResult - elseif overridesResult._IsModUtilOverride then - return overridesResult._Value - elseif type( baseResult ) == "table" then - return makeOverrideTable( baseResult, overridesResult ) - else - return makeOverrideTable( { }, overridesResult ) - end - end, - __newindex = function( self, name, value ) - local currentOverride = self._Overrides[ name ] - if currentOverride == nil then - self._BaseTable[ name ] = value - elseif currentOverride._IsModUtilOverride then - currentOverride._Value = value - else - self._BaseTable[ name ] = value - end - end -} + decorators[ out ] = { Base = base, Func = func, Mod = mod } + return out +end ) ---[[ - Override the entry at indexArray in table with value. - table - the table whose entry should be overridden - must come from a previous call to makeOverrideTable( ) - indexArray - the list of indexes - value - the value to override -]] -local function setOverride( table, indexArray, value ) - if type( table ) ~= "table" or not table._IsModUtilOverrideTable then return end - ModUtil.SafeSet( table._Overrides, indexArray, { _IsModUtilOverride = true, _Value = value } ) +function ModUtil.Wrap( base, wrap, mod ) + return ModUtil.Decorate( base, wrapDecorator( wrap ), mod ) end ---[[ - Remove the override entry at indexArray in table. - No effect if the indexArray does not identify an override point. - table - the table whose override should be removed - indexArray - the list of indexes -]] -local function removeOverride( table, indexArray ) - if type( table ) ~= "table" or not table._IsModUtilOverrideTable then return end - local currentOverride = ModUtil.SafeGet( table._Overrides, indexArray ) - if currentOverride ~= nil and currentOverride._IsModUtilOverride then - ModUtil.SafeSet( table._Overrides, indexArray, nil ) +function ModUtil.Decorate.Pop( obj ) + local callback = decorators[ obj ] + if not callback then + error( "object has no decorators", 2 ) + return obj -- if error is ignored end + return callback.Base end ---[[ - Check whether there is an override for indexArray in table. - Only returns true for exact matches. For example, if you override CurrentRun in table T, - hasOverride( T, { "CurrentRun", "CurrentRoom" } ) will return false. - table - the table to check for an override - indexArray - the list of indexes -]] -local function hasOverride( table, indexArray ) - if type( table ) ~= "table" or not table._IsModUtilOverrideTable then return false end - local value = ModUtil.SafeGet( table._Overrides, indexArray ) - return value ~= nil and value._IsModUtilOverride +local function refresh( parent ) + local node = decorators[ parent ].Base + node = decorators[ node ] and refresh( node ) or node + local new = decorators[ parent ].Func( node ) + decorators[ new ] = { Base = node, decorators[ parent ].Func, decorators[ parent ].Mod } + return new end ---[[ - Gets the function's local (partially-overriden) environment, or - create a new one and attach it to the function if not yet present. - baseTable - the base table, on which to base the function's environment - indexArray - the list of indexes -]] -local function getFunctionEnv( baseTable, indexArray ) - local func = getBaseValueForWraps( baseTable, indexArray ) - if type( func ) ~= "function" then return nil end +function ModUtil.Decorate.Refresh( obj ) + if decorators[ obj ] then + return refresh( obj ) + end + return obj +end - ModUtil.NewTable( ModUtil.Internal.PerFunctionEnv, baseTable ) - local env = ModUtil.SafeGet( ModUtil.Internal.PerFunctionEnv[ baseTable ], indexArray ) - if not env then - env = makeOverrideTable( baseTable ) - ModUtil.SafeSet( ModUtil.Internal.PerFunctionEnv[ baseTable ], indexArray, env ) - setfenv( func, env ) +function ModUtil.Override( base, value, mod ) + if overrides[ value ] then + error( "cannot override with existing reference", 2 ) + end + local node, parent = base + while decorators[ node ] do + parent = node + node = decorators[ node ].Base + end + overrides[ value ] = { Base = node, Mod = mod } + if parent then + decorators[ parent ].Base = value end - return env + return ModUtil.Decorate.Refresh( base ) end ---[[ - Overrides the value at envIndexArray within the function referred to by - indexArray in baseTable, by replacing it with value. - Accesses to the value at envIndexArray from within other functions are - unaffected. - Generally, you should use ModUtil.BaseOverrideWithinFunction for a more - modder-friendly interface. - For example, after you do - ModUtil.OverrideWithinFunction( _G, { "CreateRoom" }, { "CurrentRun.CurrentRoom" }, ) - 1. Any reads to CurrentRun.CurrentRoom from CreateRoom will return - 2. Any writes to CurrentRun.CurrentRoom from CreateRoom will replace , which will - with the new value, which will be returned for subsequent accesses to CurrentRun.CurrentRoom. - 3. Any writes to CurrentRun.CurrentRoom from CreateRoom will not be visible from other functions. - 4. If CreateRoom performs writes within (eg. CurrentRun.CurrentRoom.Name = "foo") - these will be visible to from any function that has access to it. If you want - full isolation, make sure you pass in an object that nobody else has a reference to, eg. by - creating one fresh or making a copy). - baseTable - the base table for function and environment lookups (usually _G) - indexArray - the list of indices identifying the function whose environment is to be overridden - envIndexArray - the list of indices identifying the value to be overridden in the function's environment - value - the value with which to override -]] -function ModUtil.OverrideWithinFunction( baseTable, indexArray, envIndexArray, value ) - if not baseTable then return end - local env = getFunctionEnv( baseTable, indexArray ) - - if not env then return end - if hasOverride( env, envIndexArray ) then - -- we might have wraps to reapply - local overrideEnvIndexArray = DeepCopyTable( envIndexArray ) - table.insert( overrideEnvIndexArray, "_Value" ) - if not setBaseValueForWraps( env._Overrides, overrideEnvIndexArray, value ) then - setOverride( env, envIndexArray, value ) - end +function ModUtil.Restore( base ) + local node, parent = base + while decorators[ node ] do + parent = node + node = decorators[ node ].Base + end + if overrides[ node ] then + node = overrides[ node ].Base else - setOverride( env, envIndexArray, value ) + error( "object has no overrides", 2 ) end + if parent then + decorators[ parent ].Base = node.Base + end + return ModUtil.Decorate.Refresh( base ) end ---[[ - Remove the override at envIndexArray for the function at indexArray in baseTable, - so that reads and writes to envIndexArray have their usual effects on the base environment. - baseTable - the base table for function and environment lookups (usually _G) - indexArray - the list of indices identifying the function whose environment is to be overridden - envIndexArray - the list of indices identifying the value to be overridden in the function's environment -]] -function ModUtil.RestoreWithinFunction( baseTable, indexArray, envIndexArray ) - if not baseTable then return end - - local env = getFunctionEnv( baseTable, indexArray ) - if not env then return end +function ModUtil.Overriden( obj ) + local node = decorators[ obj ] + if not node then return obj end + return ModUtil.Overriden( node.Base ) +end - removeOverride( env, envIndexArray ) +function ModUtil.Original( obj ) + local node = decorators[ obj ] or overrides[ obj ] + if not node then return obj end + return ModUtil.Original( node.Base ) end +function ModUtil.ReferFunction( obtainer, ... ) + local args = table.pack( ... ) + return function( ... ) + return obtainer( table.unpack( args ) )( ... ) + end +end ---[[ - Wrap a function, so that you can insert code that runs before/after that function whenever - it's called from within a particular other function, and modify the return value if needed. - Generally, you should use ModUtil.WrapBaseWithinFunction for a more modder-friendly interface. - baseTable - the base table for function and environment lookups (usually _G) - indexArray - the list of indices identifying the function within with the wrap will apply - envIndexArray - the list of indices identifying the function whose calls will be wrapped - wrapFunc - the wrapping function - mod - (optional) the mod performing the wrapping, for debug purposes -]] -function ModUtil.WrapWithinFunction( baseTable, indexArray, envIndexArray, wrapFunc, mod ) - if type( wrapFunc ) ~= "function" then return end - if not baseTable then return end - local env = getFunctionEnv( baseTable, indexArray ) - if not env then return end - - if not hasOverride( env, envIndexArray ) then - -- Resolve the entry in baseTable at call time (not now), - -- in case further wraps or overrides are applied to it. - setOverride( - env, - envIndexArray, - function( ... ) - local resolvedFunc = ModUtil.SafeGet( baseTable, envIndexArray ) - return resolvedFunc( ... ) - end - ) +ModUtil.Metatables.ReferTable = { + __index = function( self, key ) + return getObjectData( self, "obtain" )( )[ key ] + end, + __newindex = function( self, key, value ) + getObjectData( self, "obtain" )( )[ key ] = value + end, + __call = function( self, ... ) + return getObjectData( self, "obtain" )( )( ... ) + end, + __len = function( self ) + return #getObjectData( self, "obtain" )( ) + end, + __next = function( self, key ) + return next( getObjectData( self, "obtain" )( ), key ) + end, + __inext = function( self, idx ) + return inext( getObjectData( self, "obtain" )( ), idx ) + end, + __pairs = function( self ) + return pairs( getObjectData( self, "obtain" )( ) ) + end, + __ipairs = function( self ) + return ipairs( getObjectData( self, "obtain" )( ) ) end +} - ModUtil.WrapFunction( env, envIndexArray, wrapFunc, mod ) +function ModUtil.ReferTable( obtainer, ... ) + local args = table.pack( ... ) + local obtain = function( ) + return obtainer( table.unpack( args ) ) + end + return ModUtil.Proxy( { obtain = obtain }, ModUtil.Metatables.ReferTable ) end ---[[ - Override the global value at the given path, when accessed from - within the global function at funcPath. - If the Value is a function, preserves the wraps - applied with ModUtil.WrapBaseWithinFunction et. al. - basePath - the path to override, as a string - envPath - the path to override, as a string - value - the new value to store at the path -]] -function ModUtil.BaseOverrideWithinFunction( funcPath, basePath, value ) - local indexArray = ModUtil.PathToIndexArray( funcPath ) - local envIndexArray = ModUtil.PathToIndexArray( basePath ) - ModUtil.OverrideWithinFunction( _G, indexArray, envIndexArray, value ) -end +-- Internal access ---[[ - Wraps the function with the path given by baseFuncPath, when it is called from - the function given by funcPath. - This lets you insert code within a function, without affecting other functions - that might make similar calls. - For example, to insert code to display a "cancel" at the point in "CreateBoonLootButtons" - where it calls IsMetaUpgradeSelected( "RerollPanelMetaUpgrade" ), do: - ModUtil.WrapBaseWithinFunction( "CreateBoonLootButtons", "IsMetaUpgradeSelected", function( baseFunc, name ) - if name == "RerollPanelMetaUpgrade" and CalcNumLootChoices( ) == 0 then - < code to display the cancel button > - return false - else - return baseFunc( name ) - end - end, YourMod ) - This provides better compatibility between mods that just wrapping IsMetaUpgradeSelected, because - you new code only executes when within CreateBoonLootButtons, so there are less chances for collisions - and side effects. - It also provides better compatibility than using BaseOverride on "CreateBoonLootButtons", since only one - override for a function can be active at a time, but multiple wraps can be active. - funcPath - the (global) path to the function within which the wrap will apply, as a string - for most SGG-provided functions, this is just the function's name - eg. "CreateRoomReward" or "SetTraitsOnLoot" - baseFuncPath - the (global) path to the function to wrap, as a string - for most SGG-provided functions, this is just the function's name - eg. "CreateRoomReward" or "SetTraitsOnLoot" - wrapFunc - the function to wrap around the base function - this function receives the base function as its first parameter. - all subsequent parameters should be the same as the base function - mod - (optional) the object for your mod, for debug purposes -]] -function ModUtil.WrapBaseWithinFunction( funcPath, baseFuncPath, wrapFunc, mod ) - local indexArray = ModUtil.PathToIndexArray( funcPath ) - local envIndexArray = ModUtil.PathToIndexArray( baseFuncPath ) - ModUtil.WrapWithinFunction( _G, indexArray, envIndexArray, wrapFunc, mod ) +ModUtil.Internal = ModUtil.Entangled.Union( ) + +do + local ups = ModUtil.UpValues( function( ) + return _G, + objectData, newObjectData, getObjectData, + decorators, overrides, refresh, + threadEnvironments, getEnv, replaceGlobalEnvironment, + pusherror, getname, toLookup, wrapDecorator, isNamespace, + stackLevelFunction, stackLevelInterface, stackLevelProperty, + passByValueTypes, callableCandidateTypes, excludedFieldNames + end ) + ModUtil.Entangled.Union.Add( ModUtil.Internal, ups ) end --- Final Processing -ModUtil.ReplaceGlobalEnvironment( ) +-- Final Actions + +replaceGlobalEnvironment( ) \ No newline at end of file diff --git a/modfile.txt b/modfile.txt index 087f24d..6dfa0cf 100644 --- a/modfile.txt +++ b/modfile.txt @@ -1,5 +1,10 @@ :: Mod Utility Load Priority 0 -Top Import "ModUtil.lua" -Import "ModUtilHades.lua" \ No newline at end of file + To "Scripts/Main.lua" + Top Import "ModUtil.lua" + Import "ModUtil.Extra.lua" + Import "ModUtil.Main.lua" + Import "ModUtil.Compat.lua" + To "Scripts/RoomManager.lua" + Import "ModUtil.Hades.lua" \ No newline at end of file