aboutsummaryrefslogtreecommitdiff
path: root/gamemode/nrequire.lua
blob: ba71f177f3303a00baff4854bb6185e78c6af392 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
--[[
	A replacement for require
]]
if nrequire ~= nil then return end
print("hello from nrequire!")
local path = (GM or GAMEMODE).Folder:gsub("gamemodes/","") .. "/gamemode"
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

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 pretable
]]
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.
]]
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(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",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 incyields = {}
function nrequire(req)
	print("nrequire")
	local tpath = scan(ntbl,req)
	if reqtbl[tpath] then
		print("nrequire cache hit")
		return reqtbl[tpath]
	end
	for k,v in pairs(pathstack) do
		assert(v ~= tpath,string.format("Circular dependancy detected:\n\t%s",table.concat(pathstack,"\n\t\t|\n\t\tv\n\t")))
	end
	print(string.format("Including %q\n",tpath))
	pathstack[#pathstack + 1] = tpath
	reqtbl[#reqtbl + 1] = include(tpath)
	print(string.format("Included %q\n",tpath))
	return reqtbl[#reqtbl]
	--[[
	incyields[#incyields + 1] = coroutine.create(function()
		print(string.format("Including %q\n",tpath))
		reqtbl[#reqtbl + 1] = include(tpath)
		print(string.format("Included %q\n",tpath))
	end)
	]]
end

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

	while #incyields > 0 do
		for k,v in pairs(incyields) do
			if coroutine.status(v) == "dead" then
				incyields[k] = nil
			else
				coroutine.resume(v)
			end
		end
	end
end

doincludes()
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)