SHARE
TWEET

Class (paste version)

LoganDark Feb 24th, 2020 (edited) 119 Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. --------------------------------------------------------------------------------
  2. -- Class by TerrodactyI (paste version, you can put this at the top of scripts)
  3. -- https://www.roblox.com/library/4724044278/Class
  4. --------------------------------------------------------------------------------
  5. --
  6. -- Inspired by Scrap Mechanic's class function. This library exports a single
  7. -- function that allows you to create classes. All other interaction with the
  8. -- library is performed by setting members of classes and creating instances of
  9. -- those classes.
  10. --
  11. --------------------------------------------------------------------------------
  12. --
  13. -- class(string className[, Class superclass]): Class
  14. --   Returns an empty Class with the provided className and superclass. All
  15. --   methods and fields of the superclass will be present in your class, and its
  16. --   constructors will be called when your class is instantiated.
  17. --
  18. --   Modification of the arguments passed to the constructors of a superclass is
  19. --   done using the __SuperArgs special method. See its documentation below.
  20. --
  21. --------------------------------------------------------------------------------
  22. --
  23. -- class:IsClass(any arg): bool
  24. --   Returns true if the `arg` is a Class created by this class function.
  25. --
  26. --   If this returns false, it could still be a class from a different copy of
  27. --   this module. However, it will not be identified as a class by any checks or
  28. --   functions in this script.
  29. --
  30. --------------------------------------------------------------------------------
  31. --
  32. -- class:IsInstance(any arg): bool
  33. --   Returns true if the provided argument is a ClassInstance.
  34. --
  35. --   If this returns false, it could still be an instance from a different copy
  36. --   of this module. However, it will not be identified as a class by any checks
  37. --   or functions in this script.
  38. --
  39. --------------------------------------------------------------------------------
  40. --
  41. -- class:IsClassOrInstance(any arg): bool
  42. --   Returns true if the provided argument is a Class or ClassInstance.
  43. --
  44. --   If this returns false, it could still be a class or instance from a
  45. --   different copy of this module. However, it will not be identified as such
  46. --   by any checks or functions in this script.
  47. --
  48. --------------------------------------------------------------------------------
  49. --
  50. -- Class.__Metatable: table?
  51. --   A metatable applied to all new instances of a class. All metamethods,
  52. --   including those such as __index, __newindex, __call, __tostring and so on
  53. --   should function correctly.
  54. --
  55. --   After any class or one of its subclasses has been instantiated at least
  56. --   once, its metatable will become locked. You will no longer be able to set
  57. --   the __Metatable field. However, before this happens, you are free to set or
  58. --   re-set __Metatable as many times as you want.
  59. --
  60. --   Once a metatable is locked, an immutable copy of it is created, and you can
  61. --   never change it again. Indexing __Metatable will also return an immutable
  62. --   copy of the metatable being used.
  63. --
  64. --   You do not need to define this field, it will not be used if it doesn't
  65. --   exist.
  66. --
  67. --------------------------------------------------------------------------------
  68. --
  69. -- Class.__Preconstruct(instance, ...): nil
  70. --   Called during instantiation before __SuperArgs or any superclass
  71. --   constructors are called.
  72. --
  73. --   This method will not be called if it doesn't exist.
  74. --
  75. --------------------------------------------------------------------------------
  76. --
  77. -- Class.__Construct(instance, ...): nil
  78. --   Called after __Preconstruct, __SuperArgs and all superclass constructors.
  79. --
  80. --   This method will not be called if it doesn't exist.
  81. --
  82. --------------------------------------------------------------------------------
  83. --
  84. -- Class.__SuperArgs(instance, ...): ...
  85. --   Called after __Preconstruct. Receives arguments passed to the constructor
  86. --   and returns the arguments to be passed to superclass constructors.
  87. --
  88. --   This method will not be called if it doesn't exist.
  89. --
  90. --------------------------------------------------------------------------------
  91. --
  92. -- <C: Class> C.__Class: C
  93. --   This class.
  94. --
  95. --------------------------------------------------------------------------------
  96. --
  97. -- Class.__Super: Class?
  98. --   The superclass of this class, or nil if there is none.
  99. --
  100. --   Attempting to set this field will result in an error.
  101. --
  102. --------------------------------------------------------------------------------
  103. --
  104. -- Class.__ClassName: string
  105. --   The name of this class.
  106. --
  107. --   Exercise caution in setting this value. It will be propagated correctly for
  108. --   you automatically, but expect to break systems that expect class names not
  109. --   to change.
  110. --
  111. --------------------------------------------------------------------------------
  112. --
  113. -- Class.__ClassLib: class
  114. --   Returns the class function that created this class. If you're integrating
  115. --   into an existing class system, this is recommended over requiring your own
  116. --   version of the Class ModuleScript, as multiple ModuleScripts are
  117. --   incompatible due to scoping.
  118. --
  119. --------------------------------------------------------------------------------
  120. --
  121. -- Class:__IsA(string | Class class): bool
  122. --   Returns true if this class or any of its superclasses is equal to the
  123. --   provided argument. If a string is provided, it checks the class names. If a
  124. --   Class is provided, it checks for reference equality in its superclasses.
  125. --
  126. --------------------------------------------------------------------------------
  127. --
  128. -- <C: Class> C:__Subclass(string className): Class<C>
  129. --   Returns a new subclass of this class with the provided class name.
  130. --
  131. --   Shorthand for class(className, C)
  132. --
  133. --------------------------------------------------------------------------------
  134. --
  135. -- Class:__...
  136. --   Do not attempt to set or index fields beginning with two underscores. They
  137. --   will error, as those names are reserved by this library for future
  138. --   revisions.
  139. --
  140. --------------------------------------------------------------------------------
  141. --
  142. -- <C: Class> C(...): ClassInstance<C>
  143. --   Creates a new instance of this class using the provided constructor
  144. --   arguments.
  145. --
  146. --   Constructor functions are called in an order and fashion similar to this:
  147. --
  148. --    class.__Preconstruct(instance, ...)
  149. --    local superArgs = class.__SuperArgs(instance, ...)
  150. --    class.__Super.__Preconstruct(instance, unpack(superArgs))
  151. --    -- and so on, up the tree, until there are no more superclasses left
  152. --    class.__Super.__Construct(instance, unpack(superArgs))
  153. --    class.__Construct(instance, ...)
  154. --
  155. --   The library implementation uses loops, a stack, and complex metatable
  156. --   arrangements. You can read it below, after all, this is the script
  157. --   containing it.
  158. --
  159. --------------------------------------------------------------------------------
  160. --
  161. -- ClassInstance:__Metatable
  162. -- ClassInstance:__Preconstruct
  163. -- ClassInstance:__Construct
  164. -- ClassInstance:__SuperArgs
  165. -- ClassInstance:__...
  166. --   An error is thrown if you attempt to index these at all. Do not access
  167. --   these from a class instance. You do not need to construct an instance
  168. --   manually.
  169. --
  170. --------------------------------------------------------------------------------
  171. --
  172. -- <C: Class> ClassInstance<C>:__Class: C
  173. --   The class of this class instance.
  174. --
  175. --------------------------------------------------------------------------------
  176. --
  177. -- ClassInstance:__Super: Class
  178. --   The superclass of this instance's class.
  179. --
  180. --------------------------------------------------------------------------------
  181. --
  182. -- ClassInstance:__ClassName: string
  183. --   The class name of this instance.
  184. --
  185. --------------------------------------------------------------------------------
  186. --
  187. -- ClassInstance.__ClassLib: class
  188. --   Returns the class function that created this class. If you're integrating
  189. --   into an existing class system, this is recommended over requiring your own
  190. --   version of the Class ModuleScript, as multiple ModuleScripts are
  191. --   incompatible due to scoping.
  192. --
  193. --------------------------------------------------------------------------------
  194. --
  195. -- ClassInstance:__IsA(string | Class class): bool
  196. --   Returns true if this class or any of its superclasses is equal to the
  197. --   provided argument. If a string is provided, it checks the class names. If a
  198. --   Class is provided, it checks for reference equality in its superclasses.
  199. --
  200. --------------------------------------------------------------------------------
  201. --
  202. -- <C: Class> ClassInstance<C>:__Subclass(string className): Class<C>
  203. --   Returns a new subclass of this class with the provided class name.
  204. --
  205. --   Shorthand for class(className, instance.__Class)
  206. --
  207. --------------------------------------------------------------------------------
  208. --
  209. -- That's the end of the documentation. Proceed at your own risk.
  210. --
  211. --------------------------------------------------------------------------------
  212.  
  213. local class do
  214.     local weakKeys = {__mode = 'k'}
  215.  
  216.     local classes = setmetatable({}, weakKeys)
  217.     local instances = setmetatable({}, weakKeys)
  218.  
  219.     local classMeta
  220.     local instanceMeta
  221.  
  222.     local globalPrototype
  223.     local globalClassPrototype
  224.     local globalInstancePrototype
  225.  
  226.     local deepCopy do
  227.         function deepCopy(tbl, seen)
  228.             if type(tbl) ~= 'table' then return tbl end
  229.             if type(seen) ~= 'table' then seen = {} end
  230.             if seen[tbl] then return seen[tbl] end
  231.            
  232.             local result = {}
  233.             seen[tbl] = result
  234.            
  235.             for k, v in pairs(tbl) do
  236.                 result[k] = deepCopy(v, seen)
  237.             end
  238.            
  239.             return result
  240.         end
  241.     end
  242.  
  243.     local immutableView do
  244.         local thingies = {}
  245.         local seens = {}
  246.         local immutableViewMt = {}
  247.        
  248.         function immutableViewMt:__index(key)
  249.             local value = thingies[self]
  250.            
  251.             if type(value) == 'table' then
  252.                 return immutableView(value, seens[self])
  253.             end
  254.            
  255.             return value
  256.         end
  257.        
  258.         function immutableViewMt:__newindex(key, value)
  259.             error('Immutable table', 2)
  260.         end
  261.        
  262.         function immutableViewMt:__len()
  263.             return #thingies[self]
  264.         end
  265.        
  266.         function immutableViewMt:__pairs()
  267.             return pairs(thingies[self])
  268.         end
  269.        
  270.         function immutableViewMt:__ipairs()
  271.             return ipairs(thingies[self])
  272.         end
  273.        
  274.         immutableViewMt.__metatable = '<immutable table>'
  275.        
  276.         function immutableView(tbl, seen)
  277.             if type(tbl) ~= 'table' then return tbl end
  278.             if type(seen) ~= 'table' then seen = {} end
  279.             if seen[tbl] then return seen[tbl] end
  280.            
  281.             local proxy = setmetatable({}, immutableViewMt)
  282.             thingies[proxy] = tbl
  283.             seens[proxy] = seen
  284.             seen[tbl] = proxy
  285.            
  286.             return proxy
  287.         end
  288.     end
  289.  
  290.     classMeta = {} do
  291.         function classMeta:__index(key)
  292.             local classData = classes[self]
  293.            
  294.             if key == '__Metatable' then
  295.                 if classData.metatableLocked then
  296.                     return immutableView(classData.metatable)
  297.                 end
  298.                
  299.                 return classData.metatable
  300.             elseif key == '__Preconstruct' then
  301.                 return classData.preconstructor
  302.             elseif key == '__Construct' then
  303.                 return classData.constructor
  304.             elseif key == '__SuperArgs' then
  305.                 return classData.superArgs
  306.             elseif key == '__Class' then
  307.                 return self
  308.             elseif key == '__Super' then
  309.                 return classData.superclass
  310.             elseif key == '__ClassName' then
  311.                 return classData.name
  312.             elseif key == '__ClassLib' then
  313.                 return class
  314.             elseif globalClassPrototype[key] then
  315.                 return globalClassPrototype[key]
  316.             elseif globalPrototype[key] then
  317.                 return globalPrototype[key]
  318.             elseif key:sub(1, 2) == '__' then
  319.                 error('Cannot index field beginning with __ (reserved)', 2)
  320.             end
  321.            
  322.             if classData.metatable and classData.metatable.__index then
  323.                 return classData.metatable.__index(self, key)
  324.             end
  325.            
  326.             if classData.superclass then
  327.                 return classData.superclass[key]
  328.             end
  329.         end
  330.        
  331.         function classMeta:__newindex(key, value)
  332.             local classData = classes[self]
  333.            
  334.             if key == '__Metatable' then
  335.                 if not value or type(value) == 'table' then
  336.                     if classData.metatableLocked then
  337.                         error('__Metatable is locked and cannot be changed', 2)
  338.                     end
  339.                    
  340.                     classData.metatable = value
  341.                 else
  342.                     error('__Metatable can only be set to a table or nil', 2)
  343.                 end
  344.             elseif key == '__Preconstruct' then
  345.                 if value ~= nil and type(value) ~= 'function' then
  346.                     error('__Preconstruct can only be set to a function or nil', 2)
  347.                 end
  348.                
  349.                 classData.preconstructor = value
  350.             elseif key == '__Construct' then
  351.                 if value ~= nil and type(value) ~= 'function' then
  352.                     error('__Construct can only be set to a function or nil', 2)
  353.                 end
  354.                
  355.                 classData.constructor = value
  356.             elseif key == '__SuperArgs' then
  357.                 if value ~= nil and type(value) ~= 'function' then
  358.                     error('__SuperArgs can only be set to a function or nil', 2)
  359.                 end
  360.                
  361.                 classData.superArgs = value
  362.             elseif key == '__Class' then
  363.                 error('Cannot set __Class field of class', 2)
  364.             elseif key == '__Super' then
  365.                 error('Cannot set __Super field of class', 2)
  366.             elseif key == '__ClassName' then
  367.                 classData.name = value
  368.             elseif key == '__ClassLib' then
  369.                 error('Cannot set __ClassLib field of class', 2)
  370.             elseif globalClassPrototype[key] then
  371.                 error('Cannot override global class prototype', 2)
  372.             elseif globalPrototype[key] then
  373.                 error('Cannot override global prototype', 2)
  374.             elseif key:sub(1, 2) == '__' then
  375.                 error('Cannot index field beginning with __ (reserved)', 2)
  376.             else
  377.                 if classData.metatable and classData.metatable.__newindex then
  378.                     return classData.metatable.__newindex(self, key, value)
  379.                 end
  380.                
  381.                 rawset(self, key, value)
  382.             end
  383.         end
  384.        
  385.         function classMeta:__call(...)
  386.             local classData = classes[self]
  387.            
  388.             local instance = {}
  389.             local thisInstanceMeta = classData.instanceMeta
  390.            
  391.             if not thisInstanceMeta then
  392.                 thisInstanceMeta = {}
  393.                
  394.                 local stack = {classData}
  395.                
  396.                 while stack[#stack].superclass do
  397.                     stack[#stack + 1] = classes[stack[#stack].superclass]
  398.                 end
  399.                
  400.                 for i = #stack, 1, -1 do
  401.                     local classData = stack[i]
  402.                     local newLock = not classData.metatableLocked
  403.                     classData.metatableLocked = true
  404.                    
  405.                     if classData.metatable then
  406.                         if newLock then
  407.                             classData.metatable = deepCopy(classData.metatable)
  408.                         end
  409.                        
  410.                         for k, v in pairs(classData.metatable) do
  411.                             thisInstanceMeta[k] = v
  412.                         end
  413.                     end
  414.                 end
  415.                
  416.                 for k, v in pairs(instanceMeta) do
  417.                     thisInstanceMeta[k] = v
  418.                 end
  419.                
  420.                 classData.instanceMeta = thisInstanceMeta
  421.             end
  422.            
  423.             local finalInstance = setmetatable(instance, thisInstanceMeta)
  424.             instances[finalInstance] = self
  425.            
  426.             if classData.preconstructor then
  427.                 classData.preconstructor(finalInstance, ...)
  428.             end
  429.            
  430.             local stack = {classData}
  431.             local argsStack = {{...}}
  432.            
  433.             while stack[#stack].superclass do
  434.                 local classData = stack[#stack]
  435.                 local superArgs = argsStack[#stack]
  436.                
  437.                 if classData.superArgs then
  438.                     superArgs = {classData.superArgs(finalInstance, unpack(superArgs))}
  439.                 end
  440.                
  441.                 local superclassData = classes[stack[#stack].superclass]
  442.                 stack[#stack + 1] = superclassData
  443.                 argsStack[#stack] = superArgs
  444.                
  445.                 if superclassData.preconstructor then
  446.                     superclassData.preconstructor(finalInstance, unpack(superArgs))
  447.                 end
  448.             end
  449.            
  450.             while #stack > 0 do
  451.                 local classData = stack[#stack]
  452.                 local args = argsStack[#stack]
  453.                
  454.                 if classData.constructor then
  455.                     classData.constructor(finalInstance, unpack(args))
  456.                 end
  457.                
  458.                 stack[#stack] = nil
  459.             end
  460.            
  461.             return finalInstance
  462.         end
  463.        
  464.         function classMeta:__tostring()
  465.             return self.__ClassName
  466.         end
  467.        
  468.         classMeta.__metatable = '<Class>'
  469.     end
  470.  
  471.     instanceMeta = {} do
  472.         function instanceMeta:__index(key)
  473.             local instanceClass = instances[self]
  474.             local classData = classes[instanceClass]
  475.            
  476.             if key == '__Metatable' then
  477.                 error('Cannot index __Metatable field of class through instance', 2)
  478.             elseif key == '__Preconstruct' then
  479.                 error('Cannot index __Preconstruct method of class through instance', 2)
  480.             elseif key == '__Construct' then
  481.                 error('Cannot index __Construct method of class through instance', 2)
  482.             elseif key == '__SuperArgs' then
  483.                 error('Cannot index __SuperArgs method of class through instance', 2)
  484.             elseif key == '__Class' then
  485.                 return instanceClass
  486.             elseif key == '__Super' then
  487.                 return classData.superclass
  488.             elseif key == '__ClassName' then
  489.                 return classData.name
  490.             elseif key == '__ClassLib' then
  491.                 return class
  492.             elseif globalInstancePrototype[key] then
  493.                 return globalInstancePrototype[key]
  494.             elseif globalPrototype[key] then
  495.                 return globalPrototype[key]
  496.             elseif key:sub(1, 2) == '__' then
  497.                 error('Cannot index field beginning with __ (reserved)', 2)
  498.             end
  499.            
  500.             return instanceClass[key]
  501.         end
  502.        
  503.         function instanceMeta:__newindex(key, value)
  504.             if key == '__Metatable' then
  505.                 error('Cannot index __Metatable field of class through instance', 2)
  506.             elseif key == '__Prefonstruct' then
  507.                 error('Cannot index __Preconstruct method of class through instance', 2)
  508.             elseif key == '__Construct' then
  509.                 error('Cannot index __Construct method of class through instance', 2)
  510.             elseif key == '__SuperArgs' then
  511.                 error('Cannot index __SuperArgs method of class through instance', 2)
  512.             elseif key == '__Class' then
  513.                 error('Cannot set __Class field of class instance', 2)
  514.             elseif key == '__Super' then
  515.                 error('Cannot set __Super field of class instance', 2)
  516.             elseif key == '__ClassName' then
  517.                 error('Cannot set __ClassName field of class instance', 2)
  518.             elseif key == '__ClassLib' then
  519.                 error('Cannot set __ClassLib field of class instance', 2)
  520.             elseif globalInstancePrototype[key] then
  521.                 error('Cannot override global instance prototype', 2)
  522.             elseif globalPrototype[key] then
  523.                 error('Cannot override global prototype', 2)
  524.             elseif key:sub(1, 2) == '__' then
  525.                 error('Cannot index field beginning with __ (reserved)', 2)
  526.             end
  527.            
  528.             rawset(self, key, value)
  529.         end
  530.        
  531.         function instanceMeta:__tostring()
  532.             local classData = classes[instances[self]]
  533.            
  534.             if classData.metatable and classData.metatable.__tostring then
  535.                 return classData.metatable.__tostring()
  536.             end
  537.            
  538.             return classData.name .. '()'
  539.         end
  540.        
  541.         instanceMeta.__metatable = '<ClassInstance>'
  542.     end
  543.  
  544.     globalPrototype = {} do
  545.         function globalPrototype:__IsA(class)
  546.             if not classes[class] and type(class) ~= 'string' then
  547.                 error('Must call __IsA method with a class or class name', 2)
  548.             end
  549.            
  550.             local current = self.__Class
  551.            
  552.             while current ~= nil do
  553.                 if current == class or current.__ClassName == class then return true end
  554.                
  555.                 current = current.__Super
  556.             end
  557.            
  558.             return false
  559.         end
  560.        
  561.         function globalPrototype:__Subclass(name)
  562.             return class(name, self.__Class)
  563.         end
  564.     end
  565.  
  566.     globalClassPrototype = {}
  567.     globalInstancePrototype = {}
  568.  
  569.     class = setmetatable({
  570.         IsClass = function(self, arg)
  571.             return classes[arg] ~= nil
  572.         end,
  573.         IsInstance = function(self, arg)
  574.             return instances[arg] ~= nil
  575.         end,
  576.         IsClassOrInstance = function(self, arg)
  577.             return self:IsClass(arg) or self:IsInstance(arg)
  578.         end,
  579.         __InternalState__CAUTION_DO_NOT_USE_YOU_DO_NOT_NEED_THIS__ = {
  580.             classes = classes,
  581.             instances = instances,
  582.            
  583.             classMeta = classMeta,
  584.             instanceMeta = instanceMeta,
  585.            
  586.             globalPrototype = globalPrototype,
  587.             globalClassPrototype = globalClassPrototype,
  588.             globalInstancePrototype = globalInstancePrototype
  589.         }
  590.     }, {
  591.         __call = function(self, name, superclass)
  592.             if type(name) ~= 'string' then
  593.                 error('Class must have a name', 2)
  594.             end
  595.            
  596.             if superclass ~= nil and not classes[superclass] then
  597.                 error('Superclass is not a class', 2)
  598.             end
  599.            
  600.             local class = {}
  601.             local classData = {
  602.                 name = name,
  603.                 superclass = superclass,
  604.                 metatable = nil,
  605.                 metatableLocked = false,
  606.                 instanceMeta = nil,
  607.                 preconstructor = nil,
  608.                 constructor = nil,
  609.                 superArgs = nil
  610.             }
  611.            
  612.             classes[class] = classData
  613.            
  614.             return setmetatable(class, classMeta)
  615.         end
  616.     })
  617. end
RAW Paste Data
We use cookies for various purposes including analytics. By continuing to use Pastebin, you agree to our use of cookies as described in the Cookies Policy. OK, I Understand
Top