Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local signal_src = [[
- -- -----------------------------------------------------------------------------
- -- Batched Yield-Safe Signal Implementation --
- -- This is a Signal class which has effectively identical behavior to a --
- -- normal RBXScriptSignal, with the only difference being a couple extra --
- -- stack frames at the bottom of the stack trace when an error is thrown. --
- -- This implementation caches runner coroutines, so the ability to yield in --
- -- the signal handlers comes at minimal extra cost over a naive signal --
- -- implementation that either always or never spawns a thread. --
- -- --
- -- License: --
- -- Licensed under the MIT license. --
- -- --
- -- Authors: --
- -- stravant - July 31st, 2021 - Created the file. --
- -- sleitnick - August 3rd, 2021 - Modified for Knit. --
- -- -----------------------------------------------------------------------------
- -- Signal types
- export type Connection = {
- Disconnect: (self: Connection) -> (),
- Destroy: (self: Connection) -> (),
- Connected: boolean,
- }
- export type Signal<T...> = {
- Fire: (self: Signal<T...>, T...) -> (),
- FireDeferred: (self: Signal<T...>, T...) -> (),
- Connect: (self: Signal<T...>, fn: (T...) -> ()) -> Connection,
- Once: (self: Signal<T...>, fn: (T...) -> ()) -> Connection,
- DisconnectAll: (self: Signal<T...>) -> (),
- GetConnections: (self: Signal<T...>) -> { Connection },
- Destroy: (self: Signal<T...>) -> (),
- Wait: (self: Signal<T...>) -> T...,
- }
- -- The currently idle thread to run the next handler on
- local freeRunnerThread = nil
- -- Function which acquires the currently idle handler runner thread, runs the
- -- function fn on it, and then releases the thread, returning it to being the
- -- currently idle one.
- -- If there was a currently idle runner thread already, that's okay, that old
- -- one will just get thrown and eventually GCed.
- local function acquireRunnerThreadAndCallEventHandler(fn, ...)
- local acquiredRunnerThread = freeRunnerThread
- freeRunnerThread = nil
- fn(...)
- -- The handler finished running, this runner thread is free again.
- freeRunnerThread = acquiredRunnerThread
- end
- -- Coroutine runner that we create coroutines of. The coroutine can be
- -- repeatedly resumed with functions to run followed by the argument to run
- -- them with.
- local function runEventHandlerInFreeThread(...)
- acquireRunnerThreadAndCallEventHandler(...)
- while true do
- acquireRunnerThreadAndCallEventHandler(coroutine.yield())
- end
- end
- --[=[
- @within Signal
- @interface SignalConnection
- .Connected boolean
- .Disconnect (SignalConnection) -> ()
- Represents a connection to a signal.
- ```lua
- local connection = signal:Connect(function() end)
- print(connection.Connected) --> true
- connection:Disconnect()
- print(connection.Connected) --> false
- ```
- ]=]
- -- Connection class
- local Connection = {}
- Connection.__index = Connection
- function Connection:Disconnect()
- if not self.Connected then
- return
- end
- self.Connected = false
- -- Unhook the node, but DON'T clear it. That way any fire calls that are
- -- currently sitting on this node will be able to iterate forwards off of
- -- it, but any subsequent fire calls will not hit it, and it will be GCed
- -- when no more fire calls are sitting on it.
- if self._signal._handlerListHead == self then
- self._signal._handlerListHead = self._next
- else
- local prev = self._signal._handlerListHead
- while prev and prev._next ~= self do
- prev = prev._next
- end
- if prev then
- prev._next = self._next
- end
- end
- end
- Connection.Destroy = Connection.Disconnect
- -- Make Connection strict
- setmetatable(Connection, {
- __index = function(_tb, key)
- error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2)
- end,
- __newindex = function(_tb, key, _value)
- error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2)
- end,
- })
- --[=[
- @within Signal
- @type ConnectionFn (...any) -> ()
- A function connected to a signal.
- ]=]
- --[=[
- @class Signal
- A Signal is a data structure that allows events to be dispatched
- and observed.
- This implementation is a direct copy of the de facto standard, [GoodSignal](https://devforum.roblox.com/t/lua-signal-class-comparison-optimal-goodsignal-class/1387063),
- with some added methods and typings.
- For example:
- ```lua
- local signal = Signal.new()
- -- Subscribe to a signal:
- signal:Connect(function(msg)
- print("Got message:", msg)
- end)
- -- Dispatch an event:
- signal:Fire("Hello world!")
- ```
- ]=]
- local Signal = {}
- Signal.__index = Signal
- --[=[
- Constructs a new Signal
- @return Signal
- ]=]
- function Signal.new<T...>(): Signal<T...>
- local self = setmetatable({
- _handlerListHead = false,
- _proxyHandler = nil,
- _yieldedThreads = nil,
- }, Signal)
- return self
- end
- --[=[
- Constructs a new Signal that wraps around an RBXScriptSignal.
- @param rbxScriptSignal RBXScriptSignal -- Existing RBXScriptSignal to wrap
- @return Signal
- For example:
- ```lua
- local signal = Signal.Wrap(workspace.ChildAdded)
- signal:Connect(function(part) print(part.Name .. " added") end)
- Instance.new("Part").Parent = workspace
- ```
- ]=]
- function Signal.Wrap<T...>(rbxScriptSignal: RBXScriptSignal): Signal<T...>
- assert(
- typeof(rbxScriptSignal) == "RBXScriptSignal",
- "Argument #1 to Signal.Wrap must be a RBXScriptSignal; got " .. typeof(rbxScriptSignal)
- )
- local signal = Signal.new()
- signal._proxyHandler = rbxScriptSignal:Connect(function(...)
- signal:Fire(...)
- end)
- return signal
- end
- --[=[
- Checks if the given object is a Signal.
- @param obj any -- Object to check
- @return boolean -- `true` if the object is a Signal.
- ]=]
- function Signal.Is(obj: any): boolean
- return type(obj) == "table" and getmetatable(obj) == Signal
- end
- --[=[
- @param fn ConnectionFn
- @return SignalConnection
- Connects a function to the signal, which will be called anytime the signal is fired.
- ```lua
- signal:Connect(function(msg, num)
- print(msg, num)
- end)
- signal:Fire("Hello", 25)
- ```
- ]=]
- function Signal:Connect(fn)
- local connection = setmetatable({
- Connected = true,
- _signal = self,
- _fn = fn,
- _next = false,
- }, Connection)
- if self._handlerListHead then
- connection._next = self._handlerListHead
- self._handlerListHead = connection
- else
- self._handlerListHead = connection
- end
- return connection
- end
- --[=[
- @deprecated v1.3.0 -- Use `Signal:Once` instead.
- @param fn ConnectionFn
- @return SignalConnection
- ]=]
- function Signal:ConnectOnce(fn)
- return self:Once(fn)
- end
- --[=[
- @param fn ConnectionFn
- @return SignalConnection
- Connects a function to the signal, which will be called the next time the signal fires. Once
- the connection is triggered, it will disconnect itself.
- ```lua
- signal:Once(function(msg, num)
- print(msg, num)
- end)
- signal:Fire("Hello", 25)
- signal:Fire("This message will not go through", 10)
- ```
- ]=]
- function Signal:Once(fn)
- local connection
- local done = false
- connection = self:Connect(function(...)
- if done then
- return
- end
- done = true
- connection:Disconnect()
- fn(...)
- end)
- return connection
- end
- function Signal:GetConnections()
- local items = {}
- local item = self._handlerListHead
- while item do
- table.insert(items, item)
- item = item._next
- end
- return items
- end
- -- Disconnect all handlers. Since we use a linked list it suffices to clear the
- -- reference to the head handler.
- --[=[
- Disconnects all connections from the signal.
- ```lua
- signal:DisconnectAll()
- ```
- ]=]
- function Signal:DisconnectAll()
- local item = self._handlerListHead
- while item do
- item.Connected = false
- item = item._next
- end
- self._handlerListHead = false
- --local yieldedThreads = rawget(self, "_yieldedThreads")
- local yieldedThreads = self._yieldedThreads
- if yieldedThreads then
- for thread in yieldedThreads do
- if coroutine.status(thread) == "suspended" then
- warn(debug.traceback(thread, "signal disconnected; yielded thread cancelled", 2))
- task.cancel(thread)
- end
- end
- table.clear(self._yieldedThreads)
- end
- end
- -- Signal:Fire(...) implemented by running the handler functions on the
- -- coRunnerThread, and any time the resulting thread yielded without returning
- -- to us, that means that it yielded to the Roblox scheduler and has been taken
- -- over by Roblox scheduling, meaning we have to make a new coroutine runner.
- --[=[
- @param ... any
- Fire the signal, which will call all of the connected functions with the given arguments.
- ```lua
- signal:Fire("Hello")
- -- Any number of arguments can be fired:
- signal:Fire("Hello", 32, {Test = "Test"}, true)
- ```
- ]=]
- function Signal:Fire(...)
- local item = self._handlerListHead
- while item do
- if item.Connected then
- if not freeRunnerThread then
- freeRunnerThread = coroutine.create(runEventHandlerInFreeThread)
- end
- task.spawn(freeRunnerThread, item._fn, ...)
- end
- item = item._next
- end
- end
- --[=[
- @param ... any
- Same as `Fire`, but uses `task.defer` internally & doesn't take advantage of thread reuse.
- ```lua
- signal:FireDeferred("Hello")
- ```
- ]=]
- function Signal:FireDeferred(...)
- local item = self._handlerListHead
- while item do
- local conn = item
- task.defer(function(...)
- if conn.Connected then
- conn._fn(...)
- end
- end, ...)
- item = item._next
- end
- end
- --[=[
- @return ... any
- @yields
- Yields the current thread until the signal is fired, and returns the arguments fired from the signal.
- Yielding the current thread is not always desirable. If the desire is to only capture the next event
- fired, using `Once` might be a better solution.
- ```lua
- task.spawn(function()
- local msg, num = signal:Wait()
- print(msg, num) --> "Hello", 32
- end)
- signal:Fire("Hello", 32)
- ```
- ]=]
- function Signal:Wait()
- --local yieldedThreads = rawget(self, "_yieldedThreads")
- local yieldedThreads = self._yieldedThreads
- if not yieldedThreads then
- yieldedThreads = {}
- --rawset(self, "_yieldedThreads", yieldedThreads)
- self._yieldedThreads = yieldedThreads
- end
- local thread = coroutine.running()
- yieldedThreads[thread] = true
- self:Once(function(...)
- yieldedThreads[thread] = nil
- if coroutine.status(thread) == "suspended" then
- task.spawn(thread, ...)
- end
- end)
- return coroutine.yield()
- end
- --[=[
- Cleans up the signal.
- Technically, this is only necessary if the signal is created using
- `Signal.Wrap`. Connections should be properly GC'd once the signal
- is no longer referenced anywhere. However, it is still good practice
- to include ways to strictly clean up resources. Calling `Destroy`
- on a signal will also disconnect all connections immediately.
- ```lua
- signal:Destroy()
- ```
- ]=]
- function Signal:Destroy()
- self:DisconnectAll()
- --local proxyHandler = rawget(self, "_proxyHandler")
- local proxyHandler = self._proxyHandler
- if proxyHandler then
- proxyHandler:Disconnect()
- end
- end
- -- -- im evil. make signal NOT strict >:)
- -- Make signal strict
- --setmetatable(Signal, {
- -- __index = function(_tb, key)
- -- error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2)
- -- end,
- -- __newindex = function(_tb, key, _value)
- -- error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2)
- -- end,
- --})
- return table.freeze({
- new = Signal.new,
- Wrap = Signal.Wrap,
- Is = Signal.Is,
- })
- ]]
- local init_dirs = {"/bin", "/etc", "/lib", "/boot"}
- for _, dir in init_dirs do
- fs:mkdir(dir)
- end
- --// Binaries
- fs:writefile("/bin/ls.lua", [[
- return function(args)
- local path = args[1] or fs:pwd()
- local dirs = fs:readdir(path)
- table.insert(dirs, 1, "..")
- local concatted = table.concat(dirs, " ")
- print(concatted)
- stdout("Directory listing of " .. fs:resolve(path))
- stdout(concatted)
- return -1
- end]])
- fs:writefile("/bin/mkdir.lua", [[
- return function(args)
- if not args[1] then
- stdout("No directory provided")
- return 1
- end
- fs:mkdir(args[1])
- return -1
- end]])
- fs:writefile("/bin/dl.lua", [[
- return function(args)
- local modem = require("/dev/modem")
- response = modem:GetAsync(args[1])
- fs:writefile(args[2], response)
- end]])
- fs:writefile("/bin/cat.lua", [[
- return function(args)
- stdout(fs:readfile(args[1]))
- print(fs:readfile(args[1]))
- return -1
- end]])
- fs:writefile("/bin/dbg_print_ring.lua", [[
- return function(args)
- stdout(pilot.getRing())
- return -1
- end]])
- fs:writefile("/bin/shutdown.lua", [[return function(args)
- syscall.shutdown()
- end]])
- fs:writefile("/bin/repr_test.lua", [[return function(args)
- print(require("repr")(fs._Fs))
- end]])
- fs:writefile("/boot/tasks.lua", [[return function(args)
- print("lua profile")
- end]])
- fs:writefile("/bin/trigger.lua", [[return function(args)
- syscall.trigger_port(tonumber(args[1]))
- return -1
- end]])
- fs:writefile("/bin/wipedisk.lua", [[return function(args)
- require("/dev/disk"):WriteAll{}
- syscall.shutdown()
- stdout("Disk wiped")
- return -1
- end]])
- fs:writefile("/bin/exists.lua", [[return function(args)
- stdout(fs:exists(args[1]))
- return -1
- end]])
- fs:writefile("/bin/rm.lua", [[return function(args)
- fs:remove(args[1])
- end]])
- --// Libraries
- fs:writefile("/lib/signal.lua", signal_src)
- --fs:writefile("/boot/tasks.lua", [[return function(args)
- -- syscall.exec_code(fs:readfile())
- --end]])
- local function send_inp_to_pid(pid, inp)
- local is_pid_open = fs:exists("/proc/"..pid)
- if not is_pid_open then return end
- require("/proc/"..pid.."/event/stdin"):Fire(inp)
- end
- fs:writefile("/bin/getcharge.lua", [[return function(args)
- local capacity = fs:readfile("/sys/power/capacity")
- local remaining = fs:readfile("/sys/power/remaining")
- stdout(
- math.round(
- (remaining / capacity)*100
- ) ..
- " % remaining (" .. remaining.. " / " .. capacity .. " )")
- end]])
- fs:writefile("/bin/shell.lua", [[return function(args, pid)
- print("start")
- local stdin = require("/proc/"..pid.."/event/stdin")
- while task.wait() do
- local input = stdin:Wait()
- print("Input!")
- print(input)
- if input:sub(1,1) == "\n" then input = input:sub(2) end
- local args = input:split(" ")
- local cmd = args[1]
- table.remove(args,1)
- if cmd == "pwd" then
- stdout(fs:pwd())
- elseif cmd == "cd" then
- fs:chdir(args[1])
- else
- local found_path
- local testpath_1 = "/bin/"..cmd..".lua"
- local cwd = fs:pwd()
- if cwd:sub(-1) == "/" then cwd = cwd:sub(1, -2) end
- local testpath_2 = cwd.."/"..cmd..".lua"
- print(testpath_1)
- print(testpath_2)
- if fs:exists(testpath_1) then
- found_path = testpath_1
- elseif fs:exists(testpath_2) then
- found_path = testpath_2
- end
- if not found_path then
- stdout("Binary not found.")
- continue
- end
- local pid, thread = syscall.exec_code(fs:readfile(found_path), args)
- local relayConnection = stdin:Connect(function(inp)
- send_input_to_pid(pid, inp)
- end)
- repeat task.wait() until coroutine.status(thread) == "dead"
- relayConnection:Disconnect()
- end
- end
- end]])
Advertisement
Add Comment
Please, Sign In to add comment