--[[ A thing that kinda works like require, or at least, works how I wish require worked. ]] --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")() 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 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 = {} --[[ 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) if reqtbl[tpath] then print("Cache hit! returning",reqtbl[tpath]) --for k,v in pairs(reqtbl[tpath]) do print(k,":",v) end return reqtbl[tpath] end 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 local tab_rep = {} for k = 1, #pathstack do tab_rep[k] = "\t" end print(string.format("%sIncluding %q",table.concat(tab_rep),tpath)) pathstack[#pathstack + 1] = tpath reqtbl[tpath] = include(tpath) pathstack[#pathstack] = nil print(string.format("%sIncluded %q",table.concat(tab_rep),tpath)) return reqtbl[tpath] end --[[ 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 = {} 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 doincludes() --Do it the first time through if SERVER then util.AddNetworkString("art_refresh") end if CLIENT then net.Receive("art_refresh",doincludes) end concommand.Add("art_manualrefresh",function(ply,cmd,args) doincludes() net.Start("art_refresh") net.Broadcast() end)