Repository navigation
Expand file tree
/
Copy pathmodule_manager.lua
More file actions
266 lines (232 loc) · 9.73 KB
/
Copy pathmodule_manager.lua
File metadata and controls
266 lines (232 loc) · 9.73 KB
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
_G.Core = {}
local searchFunctions = {}
for i, func in ipairs(package.loaders) do
if i == 1 then goto continue end -- skip the preloader as we don't want to mess with it
table.insert(searchFunctions, func)
::continue::
end
local shadowLoadedModules = {}
local function loadShadow(searchTerm)
local loader = nil
for i, func in ipairs(searchFunctions) do
loader = func(searchTerm)
if type(loader) == "function" then break end
end
if type(loader) ~= "function" then return loader end
local function checkModification() -- check for a modification and return true if modified
local info = love.filesystem.getInfo(searchTerm:gsub("%.", "/") .. ".lua", "file")
if not info then
-- module file no longer exists
print("Warning: shadow loaded module '" .. searchTerm .. "' file no longer exists")
return false
end
local modtime = info.modtime or 0
if shadowLoadedModules[searchTerm].latestModtime == 0 then
shadowLoadedModules[searchTerm].latestModtime = modtime
return false
end
if modtime > (shadowLoadedModules[searchTerm].latestModtime or 0) then
shadowLoadedModules[searchTerm].latestModtime = modtime
return true
end
return false
end
local function reload() -- reload only <dotpath>.lua (no folders, no init.lua)
local moduleState = shadowLoadedModules[searchTerm]
if moduleState == nil then
shadowLoadedModules[searchTerm] = { term = searchTerm, moduleResult = {} }
moduleState = shadowLoadedModules[searchTerm]
end
local path = searchTerm:gsub("%.", "/") .. ".lua"
if love.filesystem.getInfo(path, "file") == nil then
print("Warning: failed to reload '" .. searchTerm .. "': no file: " .. path)
return nil, "no file: " .. path
end
local chunk, loadErrorMessage = love.filesystem.load(path)
if type(chunk) ~= "function" then
print("Warning: failed to reload '" .. searchTerm .. "': load failed: " .. tostring(loadErrorMessage))
return nil, loadErrorMessage
end
-- Clear require cache for this term so any other code path won't get the old value.
package.loaded[searchTerm] = nil
local ok, resultOrError = pcall(chunk)
if not ok then
print("Warning: error reloading '" .. searchTerm .. "': " .. tostring(resultOrError))
return nil, resultOrError
end
local result = resultOrError
if result == nil then
print("Warning: shadow reloaded module '" .. searchTerm .. "' returned nil")
result = {}
end
moduleState.moduleResult = result
return result
end
local proxy = {}
return function(...) -- our own loader that wraps the original loader result in a proxy for hotreloading purposes
shadowLoadedModules[searchTerm] = shadowLoadedModules[searchTerm] or {
originalLoader = loader,
checkModification = checkModification,
latestModtime = 0,
term = searchTerm,
reload = reload
}
shadowLoadedModules[searchTerm].moduleResult = shadowLoadedModules[searchTerm].originalLoader(...)
if not shadowLoadedModules[searchTerm].moduleResult then
print("Warning: shadow loaded module '" .. searchTerm .. "' returned nil")
shadowLoadedModules[searchTerm].moduleResult = {}
end
if type(shadowLoadedModules[searchTerm].moduleResult) == "table" then
local proxyMeta = {
__index = function(t, k)
return shadowLoadedModules[searchTerm].moduleResult[k]
end,
__newindex = function(t, k, v)
shadowLoadedModules[searchTerm].moduleResult[k] = v
end
}
local moduleMeta = getmetatable(shadowLoadedModules[searchTerm].moduleResult)
if moduleMeta and moduleMeta.__call then
proxyMeta.__call = function(t, ...)
return shadowLoadedModules[searchTerm].moduleResult(...)
end
end
setmetatable(proxy, proxyMeta)
return proxy
elseif type(shadowLoadedModules[searchTerm].moduleResult) == "function" then
setmetatable(proxy, {
__call = function(t, ...)
return shadowLoadedModules[searchTerm].moduleResult(...)
end,
__index = function(t, k)
error("Cannot index function value '" .. searchTerm .. "'")
end,
__newindex = function(t, k, v)
error("Cannot index function value '" .. searchTerm .. "'")
end
})
else
error("Cannot shadow load module '" .. searchTerm .. "' of type " .. type(shadowLoadedModules[searchTerm].moduleResult))
end
return proxy
end
end
table.insert(package.loaders, 2, loadShadow) -- insert our custom loader into the second position of the loaders table
if IsThread then goto exclude_thread_creation_functionality end
-- creates a global thread function that is used similarly to require
function _G.Core.thread_create(...) -- creates a thread object that manages the polls and pushes for channels logic in its metatables
local args = {...}
local module_name = args[1]
if not module_name then return nil end
table.remove(args, 1)
local threadInternals = [[
IsThread = true
ModuleName = "]] .. module_name .. [["
require "module_manager"
require(ModuleName)
]]
print(threadInternals)
local thread_object = {}
-- todo: calls to the love2d thread system
local thread_code = love.thread.newThread(threadInternals)
thread_object.thread = thread_code
thread_code:start()
-- thread object needs a proxy wrapping its namespace to handle channel polling and pushing
setmetatable(thread_object, {
__index = function(t, k)
-- poll from channel
local channel = love.thread.getChannel(module_name .. "_" .. k)
if not channel then
error("Thread channel '" .. module_name .. "_" .. k .. "' does not exist for polling")
end
return channel:pop()
end,
__newindex = function(t, k, v)
-- push to channel
local channel = love.thread.getChannel(module_name .. "_" .. k)
if not channel then
error("Thread channel '" .. module_name .. "_" .. k .. "' does not exist for pushing")
end
channel:push(v)
end
})
return thread_object, _G.Core.thread_create(unpack(args)) -- return the thread object and create a new thread if there are more arguments
end
function love.threaderror(thread, errorstr)
print("Thread error in thread: " .. tostring(thread) .. ": " .. tostring(errorstr))
end
_G.thread = _G.Core.thread_create
-- this is main thread only code here
-- create a request channel to a thread object
function _G.request(loadpath)
if type(loadpath) ~= "string" then
error("request(loadpath) expects loadpath to be a string")
end
return function(channelName)
if type(channelName) ~= "string" then
error("request(loadpath)(channelName) expects channelName to be a string")
end
local fullChannelName = loadpath .. "_" .. channelName
local channel = love.thread.getChannel(fullChannelName)
if not channel then
error("Thread channel '" .. fullChannelName .. "' does not exist for pushing")
end
return function(...)
-- send all arguments as a single message payload
channel:push({ ... })
end
end
end
goto finalSection
::exclude_thread_creation_functionality::
-- this is threads only code here
do
local responseHandlers = {}
function _G.threadyield()
for i, handler in ipairs(responseHandlers) do
local channel = love.thread.getChannel(handler.channelName)
print("Checking channel: " .. handler.channelName)
print(channel)
if channel then
local message = channel:pop()
print("Popped message: " .. tostring(message))
if message then
handler.handlerFunction(message)
end
end
end
end
_G.respond = {}
setmetatable(_G.respond, {
__index = function(t, k)
-- does this channel exist
return love.thread.getChannel(k)
end,
__newindex = function(t, k, v)
print("Registering response handler for channel: " .. tostring(ModuleName) .. "_" .. tostring(k))
table.insert(responseHandlers, { channelName = tostring(ModuleName) .. "_" .. k, handlerFunction = v })
end
})
end
-- thread-only section over
::finalSection::
function _G.Core.poll_hotreload() -- hotreloads all changed modules including those loaded in threads
for k, module in pairs(shadowLoadedModules) do
if module.checkModification() then
module.reload()
end
end
--[[ extremely naughty: we are going to tack on thread printline flushing here too
local printChannel = love.thread.getChannel("printOutput")
local currentLine = true
local listToPrint = {}
while currentLine ~= nil do
currentLine = printChannel:pop()
table.insert(listToPrint, currentLine)
end
while #listToPrint > 0 do
local line = table.remove(listToPrint, 1)
print(line)
end]]
end
_G.hotreload = _G.Core.poll_hotreload -- global hotreload function that polls for changes in modules and reloads them