--[[ A thing that kinda works like require, or at least, works how I wish require worked. calls hooks: artery_nrequire_defined artery_core_loaded ]] --Don't run ourselves, or we'll get stuck in a recursive loop! if nrequire ~= nil then return end print("hello from nrequire!") local path = (GM or GAMEMODE).Folder:gsub("gamemodes/","") .. "/gamemode" --[[ Calls func on all the files under dir. ]] local function TraverseFolder(dir, func) local fpath = table.concat({path,dir,"/*"}) local files, directories = file.Find(fpath,"LUA") for k,v in pairs(files) do if string.GetExtensionFromFilename(v) == "lua" then local callpath = table.concat({path,dir,"/",v}) func(callpath) end end for k,v in pairs(directories) do local npath = table.concat({dir,"/",v}) TraverseFolder(npath,func) end end --[[ Creates a funny kind of tree. The root points to tables with file names, each file name points to a table containing the folder name it is under. If that folder is under more folders, then the folder table points to more tables in reverse order. The leaf contains the file path. Ex: { [file.lua] = { [some] = { [foldername] = "foldername/some/file.lua" } [other] = { [foldername] = "foldername/other/file.lua" } } } is created from foldername/ some/ file.lua other/ file.lua ]] local function rebuild_include_table(f) local ret = {} for k,v in pairs(f) do local pathparts = {} for part in v:gmatch("/?[%w_]+/") do pathparts[#pathparts + 1] = part:gsub("/$",""):gsub("^/","") end local filename = v:gfind("[%w_]+%.lua$")() if ret[filename] == nil then ret[filename] = {} end local cursor = ret[filename] for folder = #pathparts, 2, -1 do if cursor[pathparts[folder]] == nil then cursor[pathparts[folder]] = {} end cursor = cursor[pathparts[folder]] end assert(cursor[pathparts[1]] == nil,string.format("Found 2 files with the same path:\n\t%s\n\t%s",cursor[pathparts[1]],pathparts[1])) cursor[pathparts[1]] = v end return ret end --Finds the number of elements in a table, even if it is not an array local function tbllen(tbl) local count = 0 for k,v in pairs(tbl) do count = count + 1 end return count end --[[ Finds all the paths from a include table ]] local function collect_paths(pretbl) local ret = {} for k,v in pairs(pretbl) do if type(v) ~= "string" then local resp = collect_paths(v) for i,j in pairs(resp) do ret[#ret + 1] = j end else ret[#ret + 1] = v end end return ret end --[[ Scans the prefix table built by rebuild_include_table to find the file path for the partial name of an included file. If two files are named the same, you will need to include part of the file path until it can be resolved. ]] local function scan(pretbl, name) local pathparts = {} for part in name:gmatch("/?[%w_]+/") do pathparts[#pathparts + 1] = part:gsub("/$",""):gsub("^/","") end local filename = name:gfind("[%w_]+%.lua")() assert(filename ~= nil,string.format("Could not file a filename for %q from parts: %s valid files are:\n\t%s",name,table.concat(pathparts,","),table.concat(collect_paths(pretbl),"\n\t"))) local cursor = pretbl[filename] assert(cursor ~= nil,string.format("Scan did not find a file named %q, valid files are:\n\t%s",filename,table.concat(collect_paths(pretbl),"\n\t"))) local rev = {} for i = 1, #pathparts do rev[#pathparts - i + 1] = pathparts[i] end for k,v in ipairs(rev) do if cursor == nil then error(string.format("Scan could not complete file path translation while translateing %q at %s",table.concat(rev,","),v)) end cursor = cursor[v] end while type(cursor) ~= "string" do assert(type(cursor) ~= "nil",string.format("Could not find a valid file for path %q, file paths:\n\t%s",name,table.concat(collect_paths(pretbl),"\n\t"))) assert(tbllen(cursor) == 1,string.format("Ambiguous scan, there are two or more paths that match %q\n\t%s\nSpecify more of the file path.",name,table.concat(collect_paths(cursor),"\n\t"))) cursor = cursor[next(cursor)] end return cursor end local paths = {} local ins = function(n) paths[#paths + 1] = n end TraverseFolder("",ins) local ntbl = rebuild_include_table(paths) local reqtbl = {} for k,v in pairs(paths) do if SERVER and not v:match("/?sv_[%w_]+%.lua$") then --print("Adding CS lua file",v) AddCSLuaFile(v) end end local pathstack = {} local coroutines = {} --[[ Returns the table returned by executing a file. The table is cached after is is loaded, so calling nrequire() on a file twice will only run it once. ]] function nrequire(req) local tpath = scan(ntbl,req) --Find the full path from partial path if reqtbl[tpath] then --And just return it if we've already included it. return reqtbl[tpath] end --Otherwise, make sure we don't have a circular dependancy for k,v in pairs(pathstack) do assert(v ~= tpath,string.format("Circular dependancy detected:\n\t%s\n\t\t|\n\t\tV\n\t%s",table.concat(pathstack,"\n\t\t|\n\t\tV\n\t"),v)) end --Override print so it's easy to see what file is printing what --local tab_rep = {} --for k = 1, #pathstack do tab_rep[k] = "\t" end --print(string.format("%sIncluding %q",table.concat(tab_rep),tpath)) --local oldprint = print --print = function(...) oldprint(" ",unpack({...})) end --Deal with bookkeeping dealing with circular dependancies, and include pathstack[#pathstack + 1] = tpath reqtbl[tpath] = include(tpath) --[[ co = coroutine.create(function() reqtbl[tpath] = include(tpath) --print("Finished ", tpath) end) coroutines[#coroutines + 1] = co coroutine.resume(co) ]] pathstack[#pathstack] = nil --[[ --Try to resume everyone else waiting on something for k,v in pairs(coroutines) do --V will be nil when the coroutine finishes, which removes it from the list, nifty. coroutine.resume(v) end ]] --Undo the crazy print --print = oldprint --print(string.format("%sIncluded %q",table.concat(tab_rep),tpath)) return reqtbl[tpath] end hook.Call("artery_nrequire_defined") --[[ Automatically include all the files in the gamemode directory based on the file name. If the file starts with cl_ it will only be included on the client, if it starts with sv_ it will only be included on the server. If it starts with anything else, it will be shared. Will detect and error on circuar dependancy. ]] local function doincludes() print("doing includes") paths = {} TraverseFolder("",ins) ntbl = rebuild_include_table(paths) reqtbl = reqtbl or {} for k,v in pairs(paths) do if v:match("/?sv_[%w_]+%.lua$") then if SERVER then nrequire(v) end elseif v:match("/?cl_[%w_]+%.lua$") then if CLIENT then nrequire(v) end else nrequire(v) end pathstack = {} end end local function refresh(filename) local filepath = scan(ntbl,filename) if filepath:match("/?sv_[%w_]+%.lua$") then if SERVER then reqtbl[filepath] = nil nrequire(filepath) end elseif filepath:match("/?cl_[%w_]+%.lua$") then if CLIENT then reqtbl[filepath] = nil nrequire(filepath) end else reqtbl[filepath] = nil nrequire(filepath) end end doincludes() --Do it the first time through --Totaly refresh all files server and client side if SERVER then util.AddNetworkString("art_refresh") concommand.Add("art_manualrefresh",function(ply,cmd,args) reqtbl = {} doincludes() net.Start("art_refresh") net.Broadcast() end) end if CLIENT then net.Receive("art_refresh",doincludes) end if SERVER then util.AddNetworkString("art_reffile") concommand.Add("art_refreshfile", function(ply,cmd,args) refresh(args[1]) net.Start("art_reffile") net.WriteString(args[1]) net.Broadcast() end) end if CLIENT then net.Receive("art_reffile",function() refresh(net.ReadString()) end) end