Advertisement
LoganDark

Class (paste version)

Feb 24th, 2020
327
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 20.05 KB | None | 0 0
  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
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement