Created
November 4, 2018 09:00
-
-
Save Anaminus/1f64f3dbc0ec8ed49350298d178ccfb5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| --[[ | |
| Core | |
| Contains core functions to be used by the rest of the framework. | |
| ]] | |
| local Core = {} | |
| local stringFormat = string.format | |
| local function Pack(...) | |
| return {n = select("#", ...), ...} | |
| end | |
| Core.Pack = Pack | |
| local function countFormatSpec(s) | |
| local n = 0 | |
| local i = 0 | |
| while i <= #s do | |
| local c = s:sub(i,i) | |
| if c == "%" and i < #s then | |
| n = n + 1 | |
| if s:sub(i+1,i+1) == "%" then | |
| i = i + 1 | |
| end | |
| end | |
| i = i + 1 | |
| end | |
| return n | |
| end | |
| local function Printf(...) | |
| local ok, result = pcall(stringFormat, ...) | |
| if not ok then | |
| error(result, 2) | |
| end | |
| local level = select(countFormatSpec((...))+2, ...) or 1 | |
| if type(level) ~= "number" then | |
| error(stringFormat("level argument must be a number (got %s)", type(level)), 2) | |
| end | |
| print(result) | |
| end | |
| local function Warnf(...) | |
| local ok, result = pcall(stringFormat, ...) | |
| if not ok then | |
| error(result, 2) | |
| end | |
| local level = select(countFormatSpec((...))+2, ...) or 1 | |
| if type(level) ~= "number" then | |
| error(stringFormat("level argument must be a number (got %s)", type(level)), 2) | |
| end | |
| warn(result) | |
| end | |
| local function Errorf(...) | |
| local ok, result = pcall(stringFormat, ...) | |
| if not ok then | |
| error(result, 2) | |
| end | |
| local level = select(countFormatSpec((...))+2, ...) or 1 | |
| if type(level) ~= "number" then | |
| error(stringFormat("level argument must be a number (got %s)", type(level)), 2) | |
| end | |
| error(result, level + 1) | |
| end | |
| Core.Printf = Printf | |
| Core.Warnf = Warnf | |
| Core.Errorf = Errorf | |
| local LuaTypeNS = "lua:" | |
| local RobloxTypeNS = "rbx:" | |
| local ClassTypeNS = "class:" | |
| local ClassOfTypeNS = "classof:" | |
| local EnumTypeNS = "enum:" | |
| -- Throws a "bad argument" error. | |
| local function badArgument(arg, fn, msg, level) | |
| local err = {"bad argument", nil, nil, nil} | |
| if arg then | |
| err[#err+1] = stringFormat(" #%d", arg) | |
| end | |
| if fn then | |
| err[#err+1] = stringFormat(" to %s", fn) | |
| end | |
| if msg then | |
| err[#err+1] = stringFormat(": %s", msg) | |
| end | |
| Errorf(table.concat(err), (level or 2) + 1) | |
| end | |
| -- Returns a message indicating an unexpected type. | |
| local function BadType(got, ...) | |
| local nargs = select("#", ...) | |
| local args = {...} | |
| local types = {} | |
| if nargs > 0 then | |
| for i = 1, nargs do | |
| local t = args[i] | |
| if typeof(t) == "Enum" then | |
| types[i] = EnumTypeNS .. tostring(t) | |
| else | |
| types[i] = tostring(t) | |
| end | |
| end | |
| if #types > 1 then | |
| types[#types] = "or " .. types[#types] | |
| end | |
| else | |
| types[1] = "value" | |
| end | |
| if typeof(got) == "Enum" then | |
| got = EnumTypeNS .. tostring(got) | |
| else | |
| got = tostring(got) | |
| end | |
| return stringFormat("%s expected, got %s", table.concat(types, #types == 2 and " " or ", "), got) | |
| end | |
| Core.BadType = BadType | |
| -- Throws a "bad argument" error indicating an unexpected type. | |
| local function badArgumentType(arg, fn, got, ...) | |
| badArgument(arg, fn, BadType(got, ...), 2+1) | |
| end | |
| local function badArgumentTypeLevel(level, arg, fn, got, ...) | |
| badArgument(arg, fn, BadType(got, ...), level+1) | |
| end | |
| --[[$ | |
| function checkarg_arg(arg, fn) | |
| if not CheckArgs then return end | |
| _put(string.format([=[ | |
| if arg ~= nil and type(arg) ~= "number" then | |
| badArgumentType(%d, %q, type(arg), "number", nil) | |
| end | |
| ]=], arg, fn)) | |
| end | |
| function checkarg_fn(arg, fn) | |
| if not CheckArgs then return end | |
| _put(string.format([=[ | |
| if fn ~= nil and type(fn) ~= "string" then | |
| badArgumentType(%d, %q, type(fn), "string", nil) | |
| end | |
| ]=], arg, fn)) | |
| end | |
| function checkarg_vararg(arg, fn) | |
| if not CheckArgs then return end | |
| _put(string.format([=[ | |
| if select("#", ...) == 0 then | |
| badArgument(%d, %q, "value expected") | |
| end | |
| ]=], arg, fn)) | |
| end | |
| function checkarg_value(arg, fn) | |
| if not CheckArgs then return end | |
| end | |
| function checkarg_level(arg, fn) | |
| if not CheckArgs then return end | |
| _put(string.format([=[ | |
| if type(level) ~= "number" then | |
| badArgumentType(%d, %q, type(level), "number") | |
| end | |
| ]=], arg, fn)) | |
| end | |
| function checkarg_msg(arg, fn) | |
| if not CheckArgs then return end | |
| _put(string.format([=[ | |
| if msg ~= nil and type(msg) ~= "string" and type(msg) ~= "function" then | |
| badArgumentType(%d, %q, type(msg), "string", nil) | |
| end | |
| ]=], arg, fn)) | |
| end | |
| ]] | |
| -- Type returns the type of a value. First it tries MetaType. If that returns | |
| -- nil, then it tries LuaType. If that returns a userdata, then it returns the | |
| -- result of RobloxType. | |
| local function Type(...) | |
| --# checkarg_vararg (1, "Type") | |
| local value = ... | |
| if value == nil then | |
| return nil | |
| end | |
| -- Check __type. | |
| local mt = getmetatable(value) | |
| if type(mt) == "table" then | |
| local t = mt.__type | |
| if type(t) == "function" then | |
| t = t(value) | |
| end | |
| if t ~= nil then | |
| return tostring(t) | |
| end | |
| end | |
| -- Check type(). | |
| local t = type(value) | |
| if t ~= "userdata" then | |
| return LuaTypeNS .. t | |
| end | |
| -- Check typeof(). | |
| local t = typeof(value) | |
| if t == "Instance" then | |
| return ClassTypeNS .. value.ClassName | |
| elseif t == "EnumItem" then | |
| return value.EnumType | |
| elseif t == "userdata" then | |
| return LuaTypeNS .. t | |
| end | |
| return RobloxTypeNS .. t | |
| end | |
| -- MetaType returns the type of a value from the value's "__type" metamethod, | |
| -- converted to a string. If __type is a function, then the result of the | |
| -- function is used. Returns nil if the type could not be found. | |
| local function MetaType(...) | |
| --# checkarg_vararg (1, "MetaType") | |
| local value = ... | |
| local mt = getmetatable(value) | |
| if type(mt) == "table" then | |
| local t = mt.__type | |
| if type(t) == "function" then | |
| t = t(value) | |
| end | |
| if t ~= nil then | |
| return tostring(t) | |
| end | |
| end | |
| return nil | |
| end | |
| Core.Type = Type | |
| Core.MetaType = MetaType | |
| -- IsType returns whether a value is of one or more types. | |
| local function IsType(value, ...) | |
| local nargs = select("#", ...) | |
| if nargs == 0 then | |
| badArgument(2, "IsType", "value expected") | |
| end | |
| local args = {...} | |
| for i = 1, nargs do | |
| local t = args[i] | |
| if t == nil then | |
| if value == nil then | |
| return true, nil | |
| end | |
| elseif type(t) == "string" then | |
| local ns, typ = t:match("^(.*:)(.*)$") | |
| if ns == nil then | |
| if MetaType(value) == t then | |
| return true, t | |
| end | |
| elseif ns == LuaTypeNS then | |
| if type(value) == typ then | |
| return true, t | |
| end | |
| elseif ns == RobloxTypeNS then | |
| if typeof(value) == typ then | |
| return true, t | |
| end | |
| elseif ns == ClassTypeNS then | |
| if typeof(value) == "Instance" and value.ClassName == typ then | |
| return true, t | |
| end | |
| elseif ns == ClassOfTypeNS then | |
| if typeof(value) == "Instance" and value:IsA(typ) then | |
| return true, t | |
| end | |
| else | |
| badArgument(i+1, "IsType", stringFormat("invalid type namespace %q", ns)) | |
| end | |
| elseif typeof(t) == "Enum" then | |
| if typeof(value) == "EnumItem" and value.EnumType == t then | |
| return true, t | |
| end | |
| else | |
| badArgumentType(i+1, "IsType", Type(t), "string", "Enum", "nil") | |
| end | |
| end | |
| return false | |
| end | |
| Core.IsType = IsType | |
| local function ArgType(arg, fn, value, ...) | |
| --#if CheckArgs then | |
| --# checkarg_arg (1, "ArgType") | |
| --# checkarg_fn (2, "ArgType") | |
| --# checkarg_value (3, "ArgType") | |
| if select("#", ...) == 0 then | |
| if value == nil then | |
| badArgumentTypeLevel(2+1, arg, fn, nil) | |
| end | |
| return | |
| end | |
| local ok, t = IsType(value, ...) | |
| if ok then | |
| return t | |
| end | |
| badArgumentTypeLevel(2+1, arg, fn, Type(value), ...) | |
| --#end | |
| end | |
| local function ArgTypeLevel(level, arg, fn, value, ...) | |
| --#if CheckArgs then | |
| --# checkarg_level (1, "ArgTypeLevel") | |
| --# checkarg_arg (2, "ArgTypeLevel") | |
| --# checkarg_fn (3, "ArgTypeLevel") | |
| --# checkarg_value (4, "ArgTypeLevel") | |
| if select("#", ...) == 0 then | |
| if value == nil then | |
| badArgumentTypeLevel(level+1, arg, fn, nil) | |
| end | |
| return | |
| end | |
| local ok, t = IsType(value, ...) | |
| if ok then | |
| return t | |
| end | |
| badArgumentTypeLevel(level+1, arg, fn, Type(value), ...) | |
| --#end | |
| end | |
| Core.ArgType = ArgType | |
| Core.ArgTypeLevel = ArgTypeLevel | |
| local function AssertType(msg, value, ...) | |
| --# checkarg_msg (1, "AssertType") | |
| --# checkarg_value (2, "AssertType") | |
| local err | |
| if select("#", ...) == 0 then | |
| if value ~= nil then | |
| return | |
| end | |
| err = BadType(nil) | |
| else | |
| local ok, t = IsType(value, ...) | |
| if ok then | |
| return t | |
| end | |
| err = BadType(Type(value), ...) | |
| end | |
| if type(msg) == "function" then | |
| msg = tostring(msg()) | |
| end | |
| if msg then | |
| err = stringFormat("%s: %s", msg, err) | |
| end | |
| Errorf(err, 2) | |
| end | |
| local function AssertTypeLevel(level, msg, value, ...) | |
| --# checkarg_level (1, "AssertTypeLevel") | |
| --# checkarg_msg (2, "AssertTypeLevel") | |
| --# checkarg_value (3, "AssertTypeLevel") | |
| local err | |
| if select("#", ...) == 0 then | |
| if value ~= nil then | |
| return | |
| end | |
| err = BadType(nil) | |
| else | |
| local ok, t = IsType(value, ...) | |
| if ok then | |
| return t | |
| end | |
| err = BadType(Type(value), ...) | |
| end | |
| if type(msg) == "function" then | |
| msg = tostring(msg()) | |
| end | |
| if msg then | |
| err = stringFormat("%s: %s", msg, err) | |
| end | |
| Errorf(err, level+1) | |
| end | |
| Core.AssertType = AssertType | |
| Core.AssertTypeLevel = AssertTypeLevel | |
| local mtWeakK = {__mode = "k"} | |
| local mtWeakV = {__mode = "v"} | |
| local mtWeakKV = {__mode = "kv"} | |
| -- WeakTable creates a weak table with the given mode. Tables created with | |
| -- this function that have the same mode will share the same metatable. | |
| function Core.WeakTable(mode, t) | |
| --#if CheckArgs then | |
| ArgType(1, "WeakTable", mode, "lua:string") | |
| ArgType(2, "WeakTable", t, nil, "lua:table") | |
| --#end | |
| local k = mode:lower():match("k") | |
| local v = mode:lower():match("v") | |
| if k and v then | |
| return setmetatable(t or {}, mtWeakKV) | |
| elseif k then | |
| return setmetatable(t or {}, mtWeakK) | |
| elseif v then | |
| return setmetatable(t or {}, mtWeakV) | |
| end | |
| Errorf("mode must contain 'k' or 'v'", 2) | |
| end | |
| local function condpcall(...) | |
| local ok, cond = pcall(...) | |
| return ok and cond | |
| end | |
| -- FindFirstChild returns the first child of an instance that satisfies a | |
| -- given condition. | |
| local function FindFirstChild(instance, cond) | |
| --#if CheckArgs then | |
| ArgType(1, "FindFirstChild", instance, "classof:Instance") | |
| ArgType(2, "FindFirstChild", cond, "lua:function") | |
| --#end | |
| local children = instance:GetChildren() | |
| for i = 1, #children do | |
| if condpcall(cond, children[i]) then | |
| return children[i] | |
| end | |
| end | |
| return nil | |
| end | |
| -- WaitForChild finds or waits for a child that satisfies a given condition to | |
| -- be added to the given instance. The condition function must not yield. | |
| local function WaitForChild(instance, cond) | |
| --#if CheckArgs then | |
| ArgType(1, "WaitForChild", instance, "classof:Instance") | |
| ArgType(2, "WaitForChild", cond, "lua:function") | |
| --#end | |
| local children = instance:GetChildren() | |
| for i = 1, #children do | |
| if condpcall(cond, children[i]) then | |
| return children[i] | |
| end | |
| end | |
| while true do | |
| local child = instance.ChildAdded:Wait() | |
| if condpcall(cond, child) then | |
| return child | |
| end | |
| end | |
| end | |
| -- FindFirstAncestor returns the first ancestor of an instance that satisfies | |
| -- a given condition. | |
| local function FindFirstAncestor(instance, cond) | |
| --#if CheckArgs then | |
| ArgType(1, "FindFirstAncestor", instance, "classof:Instance") | |
| ArgType(2, "FindFirstAncestor", cond, "lua:function") | |
| --#end | |
| instance = instance.Parent | |
| while instance ~= nil and not condpcall(cond, instance) do | |
| instance = instance.Parent | |
| end | |
| return instance | |
| end | |
| -- GetChildren returns a list of all the children of an instance. An optional | |
| -- condition function filters each child. | |
| local function GetChildren(instance, cond) | |
| --#if CheckArgs then | |
| ArgType(1, "GetChildren", instance, "classof:Instance") | |
| ArgType(2, "GetChildren", cond, nil, "lua:function") | |
| --#end | |
| if not cond then | |
| return instance:GetChildren() | |
| end | |
| local results = {} | |
| local children = instance:GetChildren() | |
| for i = 1, #children do | |
| local child = children[i] | |
| if condpcall(cond, child) then | |
| results[#results+1] = child | |
| end | |
| end | |
| return results | |
| end | |
| local function getDescendants(descendants, parent) | |
| local children = parent:GetChildren() | |
| for i = 1, #children do | |
| local child = children[i] | |
| descendants[#descendants+1] = child | |
| getDescendants(descendants, child) | |
| end | |
| end | |
| local function getDescendantsCond(descendants, parent, cond) | |
| local children = parent:GetChildren() | |
| for i = 1, #children do | |
| local child = children[i] | |
| if condpcall(cond, child) then | |
| descendants[#descendants+1] = child | |
| getDescendants(descendants, child) | |
| end | |
| end | |
| end | |
| -- GetDescendants returns a top-down list of all the descendants of an | |
| -- instance. An optional condition function filters each descendant. | |
| local function GetDescendants(instance, cond) | |
| --#if CheckArgs then | |
| ArgType(1, "GetDescendants", instance, "classof:Instance") | |
| ArgType(2, "GetDescendants", cond, nil, "lua:function") | |
| --#end | |
| local descendants = {} | |
| if not cond then | |
| getDescendants(descendants, instance) | |
| return descendants | |
| end | |
| getDescendantsCond(descendants, instance, cond) | |
| return descendants | |
| end | |
| Core.FindFirstChild = FindFirstChild | |
| Core.WaitForChild = WaitForChild | |
| Core.FindFirstAncestor = FindFirstAncestor | |
| Core.GetChildren = GetChildren | |
| Core.GetDescendants = GetDescendants | |
| local manifest | |
| local findManifest = function(child) | |
| return child.Name == "Manifest" and child:IsA("StringValue") | |
| end | |
| if game:GetService("RunService"):IsServer() then | |
| manifest = { | |
| Core = {}, | |
| Client = {}, | |
| Server = {}, | |
| } | |
| do | |
| local function r(manifest, instance, name, depth) | |
| for _, child in pairs(instance:GetChildren()) do | |
| name[depth] = child.Name | |
| if child:IsA("ModuleScript") then | |
| manifest[table.concat(name, ".")] = true | |
| end | |
| r(manifest, child, name, depth+1) | |
| end | |
| name[depth] = nil | |
| end | |
| local ClientModules = FindFirstChild(game:GetService("ReplicatedStorage"), function(child) | |
| return child.Name == "Modules" and child:IsA("Folder") | |
| end) | |
| if ClientModules == nil then | |
| Errorf("missing Client Modules", 2) | |
| end | |
| local ServerModules = FindFirstChild(game:GetService("ServerScriptService"), function(child) | |
| return child.Name == "Modules" and child:IsA("Folder") | |
| end) | |
| if ServerModules == nil then | |
| Errorf("missing Server Modules", 2) | |
| end | |
| r(manifest.Core, script, {}, 1) | |
| r(manifest.Client, ClientModules, {}, 1) | |
| r(manifest.Server, ServerModules, {}, 1) | |
| end | |
| local replicator = FindFirstChild(script, findManifest) | |
| if not replicator then | |
| replicator = Instance.new("StringValue") | |
| replicator.Name = "Manifest" | |
| replicator.Parent = script | |
| end | |
| local function serialize(modules) | |
| local list = {} | |
| for module in pairs(modules) do | |
| list[#list+1] = module | |
| end | |
| table.sort(list) | |
| return table.concat(list, ",") | |
| end | |
| replicator.Value = | |
| "Core:" .. serialize(manifest.Core) .. ";" .. | |
| "Client:" .. serialize(manifest.Client) .. ";" .. | |
| "Server:" .. serialize(manifest.Server) .. ";" | |
| end | |
| if game:GetService("RunService"):IsClient() then | |
| local replicator = WaitForChild(script, findManifest) | |
| while replicator.Value == "" do | |
| replicator.Changed:Wait() | |
| end | |
| for name, modules in replicator.Value:gmatch("(%w+):(.-);") do | |
| local set = {} | |
| for module in modules:gmatch("[^,]+") do | |
| set[module] = true | |
| end | |
| manifest[name] = set | |
| end | |
| end | |
| local requireStack = {} | |
| function requireStack:push(thread, module) | |
| local stack = self[thread] | |
| if stack == nil then | |
| stack = {} | |
| self[thread] = stack | |
| end | |
| for i = 1, #stack do | |
| if stack[i] == module then | |
| Errorf("cyclic dependency detected at module %s", module:GetFullName(), 4) | |
| end | |
| end | |
| stack[#stack+1] = module | |
| end | |
| function requireStack:pop(thread, module) | |
| local stack = self[thread] | |
| if stack == nil or #stack == 0 then | |
| Errorf("attempt to pop empty stack", 2) | |
| end | |
| if stack[#stack] ~= module then | |
| Errorf("attempt to pop mismatched stack item", 2) | |
| end | |
| stack[#stack] = nil | |
| if #stack == 0 then | |
| self[thread] = nil | |
| end | |
| end | |
| local function resolveRequirePath(parent, path, manifestName) | |
| if manifestName and not manifest[manifestName][path] then | |
| Warnf("attempt to require untracked module %s:%s", manifestName, path, 2) | |
| end | |
| for name in path:gmatch("[^%.]+") do | |
| if parent == game then | |
| local ok, service = pcall(game.GetService, game, name) | |
| if ok and service then | |
| parent = service | |
| end | |
| else | |
| parent = WaitForChild(parent, function(child) | |
| return child.Name == name | |
| end) | |
| end | |
| end | |
| return parent | |
| end | |
| local function requireBase(parent, soft) | |
| local thread = coroutine.running() | |
| requireStack:push(thread, parent) | |
| local results | |
| if soft then | |
| results = Pack(pcall(require, parent)) | |
| else | |
| results = Pack(require(parent)) | |
| end | |
| requireStack:pop(thread, parent) | |
| return unpack(results, 1, results.n) | |
| end | |
| -- Require requires a module in the game tree using a dot-separated string to | |
| -- refer to the module. | |
| function Core.Require(path, soft) | |
| --#if CheckArgs then | |
| local pathT = | |
| ArgType(1, "Require", path, "lua:string", "classof:ModuleScript") | |
| ArgType(2, "Require", soft, nil, "lua:boolean") | |
| --#end | |
| if pathT == "classof:ModuleScript" then | |
| return requireBase(path, soft) | |
| end | |
| return requireBase(resolveRequirePath(game, path), soft) | |
| end | |
| -- RequireCore requires a core module using a dot-separated string to refer to | |
| -- the module. | |
| function Core.RequireCore(path, soft) | |
| --#if CheckArgs then | |
| ArgType(1, "RequireCore", path, "lua:string") | |
| ArgType(2, "RequireCore", soft, nil, "lua:boolean") | |
| --#end | |
| return requireBase(resolveRequirePath(script, path, "Core"), soft) | |
| end | |
| -- RequireClient requires a module from the client module folder, using a | |
| -- dot-separated string to refer to the module. | |
| function Core.RequireClient(path, soft) | |
| --#if CheckArgs then | |
| ArgType(1, "RequireClient", path, "lua:string") | |
| ArgType(2, "RequireClient", soft, nil, "lua:boolean") | |
| --#end | |
| local Modules = WaitForChild(game:GetService("ReplicatedFirst"), function(child) | |
| return child:IsA("Folder") and child.Name == "Modules" | |
| end) | |
| return requireBase(resolveRequirePath(Modules, path, "Client"), soft) | |
| end | |
| -- RequireServer requires a module from the server module folder, using a | |
| -- dot-separated string to refer to the module. | |
| function Core.RequireServer(path, soft) | |
| --#if CheckArgs then | |
| ArgType(1, "RequireServer", path, "lua:string") | |
| ArgType(2, "RequireServer", soft, nil, "lua:boolean") | |
| --#end | |
| local Modules = WaitForChild(game:GetService("ServerScriptService"), function(child) | |
| return child:IsA("Folder") and child.Name == "Modules" | |
| end) | |
| return requireBase(resolveRequirePath(Modules, path, "Server"), soft) | |
| end | |
| return Core |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment