Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- --------------------------------------------------------------------------------
- -- Class by TerrodactyI (paste version, you can put this at the top of scripts)
- -- https://www.roblox.com/library/4724044278/Class
- --------------------------------------------------------------------------------
- --
- -- Inspired by Scrap Mechanic's class function. This library exports a single
- -- function that allows you to create classes. All other interaction with the
- -- library is performed by setting members of classes and creating instances of
- -- those classes.
- --
- --------------------------------------------------------------------------------
- --
- -- class(string className[, Class superclass]): Class
- -- Returns an empty Class with the provided className and superclass. All
- -- methods and fields of the superclass will be present in your class, and its
- -- constructors will be called when your class is instantiated.
- --
- -- Modification of the arguments passed to the constructors of a superclass is
- -- done using the __SuperArgs special method. See its documentation below.
- --
- --------------------------------------------------------------------------------
- --
- -- class:IsClass(any arg): bool
- -- Returns true if the `arg` is a Class created by this class function.
- --
- -- If this returns false, it could still be a class from a different copy of
- -- this module. However, it will not be identified as a class by any checks or
- -- functions in this script.
- --
- --------------------------------------------------------------------------------
- --
- -- class:IsInstance(any arg): bool
- -- Returns true if the provided argument is a ClassInstance.
- --
- -- If this returns false, it could still be an instance from a different copy
- -- of this module. However, it will not be identified as a class by any checks
- -- or functions in this script.
- --
- --------------------------------------------------------------------------------
- --
- -- class:IsClassOrInstance(any arg): bool
- -- Returns true if the provided argument is a Class or ClassInstance.
- --
- -- If this returns false, it could still be a class or instance from a
- -- different copy of this module. However, it will not be identified as such
- -- by any checks or functions in this script.
- --
- --------------------------------------------------------------------------------
- --
- -- Class.__Metatable: table?
- -- A metatable applied to all new instances of a class. All metamethods,
- -- including those such as __index, __newindex, __call, __tostring and so on
- -- should function correctly.
- --
- -- After any class or one of its subclasses has been instantiated at least
- -- once, its metatable will become locked. You will no longer be able to set
- -- the __Metatable field. However, before this happens, you are free to set or
- -- re-set __Metatable as many times as you want.
- --
- -- Once a metatable is locked, an immutable copy of it is created, and you can
- -- never change it again. Indexing __Metatable will also return an immutable
- -- copy of the metatable being used.
- --
- -- You do not need to define this field, it will not be used if it doesn't
- -- exist.
- --
- --------------------------------------------------------------------------------
- --
- -- Class.__Preconstruct(instance, ...): nil
- -- Called during instantiation before __SuperArgs or any superclass
- -- constructors are called.
- --
- -- This method will not be called if it doesn't exist.
- --
- --------------------------------------------------------------------------------
- --
- -- Class.__Construct(instance, ...): nil
- -- Called after __Preconstruct, __SuperArgs and all superclass constructors.
- --
- -- This method will not be called if it doesn't exist.
- --
- --------------------------------------------------------------------------------
- --
- -- Class.__SuperArgs(instance, ...): ...
- -- Called after __Preconstruct. Receives arguments passed to the constructor
- -- and returns the arguments to be passed to superclass constructors.
- --
- -- This method will not be called if it doesn't exist.
- --
- --------------------------------------------------------------------------------
- --
- -- <C: Class> C.__Class: C
- -- This class.
- --
- --------------------------------------------------------------------------------
- --
- -- Class.__Super: Class?
- -- The superclass of this class, or nil if there is none.
- --
- -- Attempting to set this field will result in an error.
- --
- --------------------------------------------------------------------------------
- --
- -- Class.__ClassName: string
- -- The name of this class.
- --
- -- Exercise caution in setting this value. It will be propagated correctly for
- -- you automatically, but expect to break systems that expect class names not
- -- to change.
- --
- --------------------------------------------------------------------------------
- --
- -- Class.__ClassLib: class
- -- Returns the class function that created this class. If you're integrating
- -- into an existing class system, this is recommended over requiring your own
- -- version of the Class ModuleScript, as multiple ModuleScripts are
- -- incompatible due to scoping.
- --
- --------------------------------------------------------------------------------
- --
- -- Class:__IsA(string | Class class): bool
- -- Returns true if this class or any of its superclasses is equal to the
- -- provided argument. If a string is provided, it checks the class names. If a
- -- Class is provided, it checks for reference equality in its superclasses.
- --
- --------------------------------------------------------------------------------
- --
- -- <C: Class> C:__Subclass(string className): Class<C>
- -- Returns a new subclass of this class with the provided class name.
- --
- -- Shorthand for class(className, C)
- --
- --------------------------------------------------------------------------------
- --
- -- Class:__...
- -- Do not attempt to set or index fields beginning with two underscores. They
- -- will error, as those names are reserved by this library for future
- -- revisions.
- --
- --------------------------------------------------------------------------------
- --
- -- <C: Class> C(...): ClassInstance<C>
- -- Creates a new instance of this class using the provided constructor
- -- arguments.
- --
- -- Constructor functions are called in an order and fashion similar to this:
- --
- -- class.__Preconstruct(instance, ...)
- -- local superArgs = class.__SuperArgs(instance, ...)
- -- class.__Super.__Preconstruct(instance, unpack(superArgs))
- -- -- and so on, up the tree, until there are no more superclasses left
- -- class.__Super.__Construct(instance, unpack(superArgs))
- -- class.__Construct(instance, ...)
- --
- -- The library implementation uses loops, a stack, and complex metatable
- -- arrangements. You can read it below, after all, this is the script
- -- containing it.
- --
- --------------------------------------------------------------------------------
- --
- -- ClassInstance:__Metatable
- -- ClassInstance:__Preconstruct
- -- ClassInstance:__Construct
- -- ClassInstance:__SuperArgs
- -- ClassInstance:__...
- -- An error is thrown if you attempt to index these at all. Do not access
- -- these from a class instance. You do not need to construct an instance
- -- manually.
- --
- --------------------------------------------------------------------------------
- --
- -- <C: Class> ClassInstance<C>:__Class: C
- -- The class of this class instance.
- --
- --------------------------------------------------------------------------------
- --
- -- ClassInstance:__Super: Class
- -- The superclass of this instance's class.
- --
- --------------------------------------------------------------------------------
- --
- -- ClassInstance:__ClassName: string
- -- The class name of this instance.
- --
- --------------------------------------------------------------------------------
- --
- -- ClassInstance.__ClassLib: class
- -- Returns the class function that created this class. If you're integrating
- -- into an existing class system, this is recommended over requiring your own
- -- version of the Class ModuleScript, as multiple ModuleScripts are
- -- incompatible due to scoping.
- --
- --------------------------------------------------------------------------------
- --
- -- ClassInstance:__IsA(string | Class class): bool
- -- Returns true if this class or any of its superclasses is equal to the
- -- provided argument. If a string is provided, it checks the class names. If a
- -- Class is provided, it checks for reference equality in its superclasses.
- --
- --------------------------------------------------------------------------------
- --
- -- <C: Class> ClassInstance<C>:__Subclass(string className): Class<C>
- -- Returns a new subclass of this class with the provided class name.
- --
- -- Shorthand for class(className, instance.__Class)
- --
- --------------------------------------------------------------------------------
- --
- -- That's the end of the documentation. Proceed at your own risk.
- --
- --------------------------------------------------------------------------------
- local class do
- local weakKeys = {__mode = 'k'}
- local classes = setmetatable({}, weakKeys)
- local instances = setmetatable({}, weakKeys)
- local classMeta
- local instanceMeta
- local globalPrototype
- local globalClassPrototype
- local globalInstancePrototype
- local deepCopy do
- function deepCopy(tbl, seen)
- if type(tbl) ~= 'table' then return tbl end
- if type(seen) ~= 'table' then seen = {} end
- if seen[tbl] then return seen[tbl] end
- local result = {}
- seen[tbl] = result
- for k, v in pairs(tbl) do
- result[k] = deepCopy(v, seen)
- end
- return result
- end
- end
- local immutableView do
- local thingies = {}
- local seens = {}
- local immutableViewMt = {}
- function immutableViewMt:__index(key)
- local value = thingies[self]
- if type(value) == 'table' then
- return immutableView(value, seens[self])
- end
- return value
- end
- function immutableViewMt:__newindex(key, value)
- error('Immutable table', 2)
- end
- function immutableViewMt:__len()
- return #thingies[self]
- end
- function immutableViewMt:__pairs()
- return pairs(thingies[self])
- end
- function immutableViewMt:__ipairs()
- return ipairs(thingies[self])
- end
- immutableViewMt.__metatable = '<immutable table>'
- function immutableView(tbl, seen)
- if type(tbl) ~= 'table' then return tbl end
- if type(seen) ~= 'table' then seen = {} end
- if seen[tbl] then return seen[tbl] end
- local proxy = setmetatable({}, immutableViewMt)
- thingies[proxy] = tbl
- seens[proxy] = seen
- seen[tbl] = proxy
- return proxy
- end
- end
- classMeta = {} do
- function classMeta:__index(key)
- local classData = classes[self]
- if key == '__Metatable' then
- if classData.metatableLocked then
- return immutableView(classData.metatable)
- end
- return classData.metatable
- elseif key == '__Preconstruct' then
- return classData.preconstructor
- elseif key == '__Construct' then
- return classData.constructor
- elseif key == '__SuperArgs' then
- return classData.superArgs
- elseif key == '__Class' then
- return self
- elseif key == '__Super' then
- return classData.superclass
- elseif key == '__ClassName' then
- return classData.name
- elseif key == '__ClassLib' then
- return class
- elseif globalClassPrototype[key] then
- return globalClassPrototype[key]
- elseif globalPrototype[key] then
- return globalPrototype[key]
- elseif key:sub(1, 2) == '__' then
- error('Cannot index field beginning with __ (reserved)', 2)
- end
- if classData.metatable and classData.metatable.__index then
- return classData.metatable.__index(self, key)
- end
- if classData.superclass then
- return classData.superclass[key]
- end
- end
- function classMeta:__newindex(key, value)
- local classData = classes[self]
- if key == '__Metatable' then
- if not value or type(value) == 'table' then
- if classData.metatableLocked then
- error('__Metatable is locked and cannot be changed', 2)
- end
- classData.metatable = value
- else
- error('__Metatable can only be set to a table or nil', 2)
- end
- elseif key == '__Preconstruct' then
- if value ~= nil and type(value) ~= 'function' then
- error('__Preconstruct can only be set to a function or nil', 2)
- end
- classData.preconstructor = value
- elseif key == '__Construct' then
- if value ~= nil and type(value) ~= 'function' then
- error('__Construct can only be set to a function or nil', 2)
- end
- classData.constructor = value
- elseif key == '__SuperArgs' then
- if value ~= nil and type(value) ~= 'function' then
- error('__SuperArgs can only be set to a function or nil', 2)
- end
- classData.superArgs = value
- elseif key == '__Class' then
- error('Cannot set __Class field of class', 2)
- elseif key == '__Super' then
- error('Cannot set __Super field of class', 2)
- elseif key == '__ClassName' then
- classData.name = value
- elseif key == '__ClassLib' then
- error('Cannot set __ClassLib field of class', 2)
- elseif globalClassPrototype[key] then
- error('Cannot override global class prototype', 2)
- elseif globalPrototype[key] then
- error('Cannot override global prototype', 2)
- elseif key:sub(1, 2) == '__' then
- error('Cannot index field beginning with __ (reserved)', 2)
- else
- if classData.metatable and classData.metatable.__newindex then
- return classData.metatable.__newindex(self, key, value)
- end
- rawset(self, key, value)
- end
- end
- function classMeta:__call(...)
- local classData = classes[self]
- local instance = {}
- local thisInstanceMeta = classData.instanceMeta
- if not thisInstanceMeta then
- thisInstanceMeta = {}
- local stack = {classData}
- while stack[#stack].superclass do
- stack[#stack + 1] = classes[stack[#stack].superclass]
- end
- for i = #stack, 1, -1 do
- local classData = stack[i]
- local newLock = not classData.metatableLocked
- classData.metatableLocked = true
- if classData.metatable then
- if newLock then
- classData.metatable = deepCopy(classData.metatable)
- end
- for k, v in pairs(classData.metatable) do
- thisInstanceMeta[k] = v
- end
- end
- end
- for k, v in pairs(instanceMeta) do
- thisInstanceMeta[k] = v
- end
- classData.instanceMeta = thisInstanceMeta
- end
- local finalInstance = setmetatable(instance, thisInstanceMeta)
- instances[finalInstance] = self
- if classData.preconstructor then
- classData.preconstructor(finalInstance, ...)
- end
- local stack = {classData}
- local argsStack = {{...}}
- while stack[#stack].superclass do
- local classData = stack[#stack]
- local superArgs = argsStack[#stack]
- if classData.superArgs then
- superArgs = {classData.superArgs(finalInstance, unpack(superArgs))}
- end
- local superclassData = classes[stack[#stack].superclass]
- stack[#stack + 1] = superclassData
- argsStack[#stack] = superArgs
- if superclassData.preconstructor then
- superclassData.preconstructor(finalInstance, unpack(superArgs))
- end
- end
- while #stack > 0 do
- local classData = stack[#stack]
- local args = argsStack[#stack]
- if classData.constructor then
- classData.constructor(finalInstance, unpack(args))
- end
- stack[#stack] = nil
- end
- return finalInstance
- end
- function classMeta:__tostring()
- return self.__ClassName
- end
- classMeta.__metatable = '<Class>'
- end
- instanceMeta = {} do
- function instanceMeta:__index(key)
- local instanceClass = instances[self]
- local classData = classes[instanceClass]
- if key == '__Metatable' then
- error('Cannot index __Metatable field of class through instance', 2)
- elseif key == '__Preconstruct' then
- error('Cannot index __Preconstruct method of class through instance', 2)
- elseif key == '__Construct' then
- error('Cannot index __Construct method of class through instance', 2)
- elseif key == '__SuperArgs' then
- error('Cannot index __SuperArgs method of class through instance', 2)
- elseif key == '__Class' then
- return instanceClass
- elseif key == '__Super' then
- return classData.superclass
- elseif key == '__ClassName' then
- return classData.name
- elseif key == '__ClassLib' then
- return class
- elseif globalInstancePrototype[key] then
- return globalInstancePrototype[key]
- elseif globalPrototype[key] then
- return globalPrototype[key]
- elseif key:sub(1, 2) == '__' then
- error('Cannot index field beginning with __ (reserved)', 2)
- end
- return instanceClass[key]
- end
- function instanceMeta:__newindex(key, value)
- if key == '__Metatable' then
- error('Cannot index __Metatable field of class through instance', 2)
- elseif key == '__Prefonstruct' then
- error('Cannot index __Preconstruct method of class through instance', 2)
- elseif key == '__Construct' then
- error('Cannot index __Construct method of class through instance', 2)
- elseif key == '__SuperArgs' then
- error('Cannot index __SuperArgs method of class through instance', 2)
- elseif key == '__Class' then
- error('Cannot set __Class field of class instance', 2)
- elseif key == '__Super' then
- error('Cannot set __Super field of class instance', 2)
- elseif key == '__ClassName' then
- error('Cannot set __ClassName field of class instance', 2)
- elseif key == '__ClassLib' then
- error('Cannot set __ClassLib field of class instance', 2)
- elseif globalInstancePrototype[key] then
- error('Cannot override global instance prototype', 2)
- elseif globalPrototype[key] then
- error('Cannot override global prototype', 2)
- elseif key:sub(1, 2) == '__' then
- error('Cannot index field beginning with __ (reserved)', 2)
- end
- rawset(self, key, value)
- end
- function instanceMeta:__tostring()
- local classData = classes[instances[self]]
- if classData.metatable and classData.metatable.__tostring then
- return classData.metatable.__tostring()
- end
- return classData.name .. '()'
- end
- instanceMeta.__metatable = '<ClassInstance>'
- end
- globalPrototype = {} do
- function globalPrototype:__IsA(class)
- if not classes[class] and type(class) ~= 'string' then
- error('Must call __IsA method with a class or class name', 2)
- end
- local current = self.__Class
- while current ~= nil do
- if current == class or current.__ClassName == class then return true end
- current = current.__Super
- end
- return false
- end
- function globalPrototype:__Subclass(name)
- return class(name, self.__Class)
- end
- end
- globalClassPrototype = {}
- globalInstancePrototype = {}
- class = setmetatable({
- IsClass = function(self, arg)
- return classes[arg] ~= nil
- end,
- IsInstance = function(self, arg)
- return instances[arg] ~= nil
- end,
- IsClassOrInstance = function(self, arg)
- return self:IsClass(arg) or self:IsInstance(arg)
- end,
- __InternalState__CAUTION_DO_NOT_USE_YOU_DO_NOT_NEED_THIS__ = {
- classes = classes,
- instances = instances,
- classMeta = classMeta,
- instanceMeta = instanceMeta,
- globalPrototype = globalPrototype,
- globalClassPrototype = globalClassPrototype,
- globalInstancePrototype = globalInstancePrototype
- }
- }, {
- __call = function(self, name, superclass)
- if type(name) ~= 'string' then
- error('Class must have a name', 2)
- end
- if superclass ~= nil and not classes[superclass] then
- error('Superclass is not a class', 2)
- end
- local class = {}
- local classData = {
- name = name,
- superclass = superclass,
- metatable = nil,
- metatableLocked = false,
- instanceMeta = nil,
- preconstructor = nil,
- constructor = nil,
- superArgs = nil
- }
- classes[class] = classData
- return setmetatable(class, classMeta)
- end
- })
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement