class & interface

March 10, 2025 ยท View on GitHub

The classes are types that abstracted from a group of similar objects. The objects generated by the classes are tables with fixed meta-tables.

The class and interface only provide methods, meta-methods, construction and deconstruction to the objects. Things like property, event are registered as object features from outside.

Table of Contents

object method

We can declare object methods in the class's definition, so all its objects or child classes's objects can use them through their meta-tables.

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		function SetName(self, name)
			self.name = name
		end

		function GetName(self, name)
			return self.name
		end
	end)

	Ann = Person()
	Ann:SetName("Ann")
	print("Hello " .. Ann:GetName())       -- Hello Ann

	Ben = Person{ name = "Ben" }
	print("Hello " .. Person.GetName(Ben)) -- Hello Ben
end)

Like the struct, the formal definition body of the class should be a function with _ENV as its first parameter.

In the definition, the global declared functions will be registered as the class's object method. The first parameter of those functions must be self to receive the objects.

When the definition is done, the meta-table of the class's objects will be generated, for a simple class like the Person, it has only object methods, so its object's meta-table will be:

{
	__index = { SetName = Person.SetName, GetName = Person.GetName },
	__metatable = Person,
}

We can create objects from the Person class by use it like a function, if we pass a table(also called the init table) as the parameter, the key-value pairs will be copied to the created object(not rawset, use obj[key] = value directly).

The Person class can access its object methods directly, but we need pass the object as the method's first argument.

object construction and the overload system

In the previous example, we use Person() or Person{ name = "Ben" } to create objects, that's the default behavior of the object creation.

__ctor

For classes, we could pass in several arguments to init the objects, that's done by declaring constructor:

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		function __ctor(self, name, age)
			self.name = name
			self.age = age
		end
	end)

	Ann = Person("Ann", 12)

	-- Ann  12
	print(Ann.name, Ann.age)

	Ben = Person{ name = "Ben" }

	-- table: 00DA7F50
	print(Ben.name)
end)

We can use the __ctor or the class name to declare the constructor, when an object is created by the class, the object and all arguments will be send to the constructor, so we can finish the init jobs to the created object.

When the constructor is defined, we can't use the init table or empty arguments to create the object, otherwise we could cause errors. To avoid the spreading of those errors, we need to validate the arguments and raise the error to the caller.

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		function __ctor(self, name, age)
			if type(name) ~= "string" then
				error("Usage: Person(name, age) - the name must be string")
			end

			if type(age) ~= "number" then
				error("Usage: Person(name, age) - the age must be number")
			end

			self.name = name
			self.age = age
		end
	end)

	Ann = Person("Ann", 12)

	-- Ann  12
	print(Ann.name, Ann.age)

	-- Error: Usage: Person(name, age) - the name must be string
	Ben = Person(12)

	-- Error: Usage: Person(name, age) - the age must be number
	Ben = Person("Ben")
end)

There are some disadvantages to the argument validation:

  • We can't use error(msg, stack) to raise the error message to the caller since the stack is unknown(with the inheritance system).

  • When our project is fully test and released, those validation is useless, it's hard to remove all of them from the codes.

  • For complex arguments sets like (name, age), (name), (age), it's hard to maintain the codes.

To simple the validation to functions, the PLoop provide the __Arguments__ attribute :

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		__Arguments__{ String, Number }
		function __ctor(self, name, age)
			self.name = name
			self.age = age
		end
	end)

	Ann = Person("Ann", 12)

	-- Ann  12
	print(Ann.name, Ann.age)

	-- Error: Usage: Person(System.String, System.Number) - the 1st argument must be string, got table
	Ben = Person{ name = "Ben" }

	-- Error: Usage: Person(System.String, System.Number) - the 2nd argument can't be nil
	Ben = Person("Ben")
end)

The String and Number are struct types for string and number data values. We could use any validatable data types like enum, struct, class and interface within the __Arguments__ attribute, so we don't need to check those arguments by ourselves.

Since the type validation is handled by the system, the error will be raised as an exception, and the system'll raise the exception at the caller. Also if we need raise an error to the caller, we can use the throw keyword:

require "PLoop" (function(_ENV)
	class "A" (function(_ENV)
		function __ctor(self)
			throw("Something error")
		end
	end)

	-- Error: something error
	o = A()
end)

With the throw keyword, the system know it'd be raised to the caller.

When the project is released, we can turn off the type validation with the platform setting(declared before require the PLoop):

PLOOP_PLATFORM_SETTINGS = {
	TYPE_VALIDATION_DISABLED = true,
}

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		__Arguments__{ String, Number }
		function __ctor(self, name, age)
			self.name = name
			self.age = age
		end
	end)

	Ann = Person("Ann", 12)

	-- Ann  12
	print(Ann.name, Ann.age)

	-- no error
	Ben = Person{ name = "Ben" }

	-- no error
	Ben = Person("Ben")
end)

We also can use the __Arguments__ to create multi constructors for different argument sets:

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		__Arguments__{ String, Number }
		function __ctor(self, name, age)
			self.name = name
			self.age = age
		end

		__Arguments__{ String }
		function __ctor(self, name)
			this(self, name, 1)
		end

		__Arguments__{ Number }
		function __ctor(self, age)
			this(self, "anonymous", age)
		end
	end)

	-- anonymous
	print(Person(100).name)

	-- 1
	print(Person("Ann").age)

	Ben = Person("Ben", 30)

	-- Ben  30
	print(Ben.name, Ben.age)
end)

This is normally called as the overload system, one method has several implementations.

Besides the constructor, we also could use the overload system for object method, static method and meta-method defined in the struct, class and interface(all features marked with System.AttributeTargets.Method)

For the constructor, we can use this(self, ...) to call other constructor implementations to simple the works, and only can use this for the constructors(also include the __new and __exist)

Since we only apply default values for the name and age in those constructors, we can combine them with the optional variable style:

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		__Arguments__{ String/"anonymous", Number/1 }
		function __ctor(self, name, age)
			self.name = name
			self.age = age
		end
	end)

	-- anonymous
	print(Person(nil, 100).name)

	-- 1
	print(Person("Ann").age)

	Ben = Person("Ben", 30)

	-- Ben  30
	print(Ben.name, Ben.age)

	nobody = Person()

	-- anonymous  1
	print(nobody.name, nobody.age)
end)

The type/default style is used to declare optional variable, we also can use type/nil if there is no default value.

The optional variable must be used after the required variables, or an error would be raised.

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		-- Error : Usage: __Arguments__{ ... } - the non-optional variables must exist before the optional variables
		__Arguments__{ String/"anonymous", Number }
		function __ctor(self, name, age)
			self.name = name
			self.age = age
		end
	end)
end)

__field

In the previous examples, we only init fields of the object with arguments, we also can declare those fields directly:

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		-- the __field is a meta-data used to declare the default fields
		__field = { name = "anonymous" }

		-- we also can use "field" as keyword to declare the fields
		-- it won't discard the previous field settings
		field { age = 1 }
	end)

	-- anonymous
	print(Person{ age = 10 }.name)

	-- 1
	print(Person{ name = "Ann" }.age)

	Ben = Person{ name = "Ben", age = 30 }

	-- Ben  30
	print(Ben.name, Ben.age)

	nobody = Person()

	-- anonymous  1
	print(nobody.name, nobody.age)
end)

So we can re-use the init-table to create the objects, the field settings are only used as the default values. It's very useful with the restrict coding mode of the PLoop, we'll see it in other chapters.

__new

The __ctor will receive the created objects, the objects are tables created by the system with the fixed meta-table. But for classes like List:

require "PLoop" (function(_ENV)
	class "List" (function(_ENV)
		function __ctor(self, ...)
			for i = 1, select("#", ...) do
				self[i] = select(i, ...)
			end
		end
	end)

	lst = List(1, 2, 3, ...)
end)

Fill all arguments into the list object will be a greater cost to the system, if we can create the object by our own, it'll be more simple, here is the __new meta-method.

require "PLoop" (function(_ENV)
	class "List" (function(_ENV)
		function __new(cls, ...)
			return { ... }, true
		end
	end)

	lst = List(1, 2, 3, ...)
end)

The __new method will receive the class and all arguments, it'd return a table(without the meta-table) as the new object, the system will set the meta-table to the returned object. The second return value means whether discard the arguments since they are handled by the __new method.

The object will be created and filled with data at once, there is no more iterator operations.

We also can use the __Arguments__ to control the arguments types for the List class:

require "PLoop" (function(_ENV)
	class "List" (function(_ENV)
		__Arguments__{ Number * 2 }
		function __new(cls, ...)
			return { ... }, true
		end
	end)

	-- Error: Usage: List(... [*2] as System.Number) - the ... must contains at least 2 arguments
	lst = List(1)

	-- Error: Usage: List(... [*2] as System.Number) - the 4th argument must be number, got string
	lst = List(1, 2, 3, "hi")

	-- 5
	print(#List(1, 2, 3, 4, 5))
end)

The type * N means the varargs variable, the N means the minimal count, so normally we use type * 0 which means there could be any number values of the types.

__exist

To reduce the cost of the object creation, we may try to re-use the objects:

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		_Persons = {}

		function __exist(cls, name)
			return _Persons[name]
		end

		function __ctor(self, name)
			_Persons[name] = self

			self.name = name
		end
	end)

	Ann = Person("Ann")

	-- true
	print(Ann == Person("Ann"))
end)

The __exist will receive the class and all arguments, so it can check whether there is an object created with the same arguments and return the object instead of creating a new one.

the construction of an object

So we have __exist, __new, __field and __ctor, they are all used during the creation of the object, the fake code for the object's construction is :

-- Check whether existed
local object = __exist(cls, ...)
if object then return object end

-- Get a table as the new object
object = __new(cls, ...) or {}

-- Clone the field
copy(object, __field)

-- Wrap to the object
setmetatable(object, objMeta)

-- Call the constructor
__ctor(object, ...)

return object

the __new with init-table

Normally we can't use init-table if we declared the constructor of the class, but if we only decalred the __new without discarding the argument, we still can use the init-table like

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		function __new(_, ...) return {} end
	end)

	o = Person{ name = "Ann" }

	print(o.name)  -- Ann
end)

object deconstruction

Normally, the Lua GC will handle the unused objects very well, but we still need manually dispose the object to erase its reference so the GC can collect it.

We can use __dtor or Dispose to define the deconstructor method of the class objects, the deconstructor will only receive the object as its argument.

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		_Persons = {}

		function __dtor(self)
			_Persons[self.name] = nil
		end

		function __exist(self, name)
			return _Persons[name]
		end

		function __ctor(self, name)
			self.name = name
			_Persons[name] = self
		end
	end)

	Ann = Person("Ann")

	-- true
	print(Ann == Person("Ann"))

	Ann:Dispose()

	-- false
	print(Ann == Person("Ann"))
end)

If the class has define the deconstructor, the object can use Dispose method to dispose itself(so don't use Dispose for other usages). We'll see more about it at later.

Nomrally when an object is disposed, its contents will be wiped out, and its Disposed field will be set to true as a mark. But in some scenarios, we may want recycle the object, we can clear its relationship by the dispose method, but we may want to keep its contents or handle them by the class itself. We can use an attribute System.__Recyclable__ to the class:

require "PLoop" (function(_ENV)
	class "Person" { __dtor = function(self) print("Dispose " .. self.Name) end }

	Ann = Person{ Name = "Ann" }

	-- Dispose Ann
	Ann:Dispose()

	-- nil  true
	print(Ann.Name, Ann.Disposed)

	__Recyclable__()
	class "Person" { }

	Ann = Person{ Name = "Ann" }

	-- Dispose Ann
	Ann:Dispose()

	-- Ann  nil
	print(Ann.Name, Ann.Disposed)
end)

meta-data

The class system is used to build the meta-table for objects, so it also support all meta-method of the Lua:

require "PLoop" (function(_ENV)
	class "Delegate" (function(_ENV)
		__Arguments__{ Function }
		function __add(self, func)
			for i = 1, #self do
				if self[i] == func then return end
			end
			self[#self + 1] = func
			return self
		end

		__Arguments__{ Function }
		function __sub(self, func)
			for i = #self, 1 do
				if self[i] == func then
					table.remove(self, i)
					break
				end
			end
			return self
		end

		function __call(self, ...)
			for i = 1, #self do
				self[1](...)
			end
		end
	end)

	del = Delegate()

	del = del + print

	-- 1  2  3
	del(1, 2, 3)

	del = del - print

	-- nothing happen
	del(1, 2, 3)
end)

Besides those meta-method, the PLoop also use __new, __ctor, __dtor, __field and __exist as it's meta-data(not only method), here is the full list:

KeyDescription
__addthe addition operation: a + b -- a is the object, also for the below operations
__subthe subtraction operation: a - b
__multhe multiplication operation: a * b
__divthe division operation: a / b
__modthe modulo operation: a % b
__powthe exponentiation operation: a ^ b
__unmthe negation operation: - a
__idivthe floor division operation: a // b
__bandthe bitwise AND operation: a & b
__borthe bitwise OR operation: a
__bxorthe bitwise exclusive OR operation: a~b
__bnotthe bitwise NOToperation: ~a
__shlthe bitwise left shift operation: a<<b
__shrthe bitwise right shift operation: a>>b
__concatthe concatenation operation: a..b
__lenthe length operation: #a
__eqthe equal operation: a == b
__ltthe less than operation: a < b
__lethe less equal operation: a <= b
__indexthe indexing access: return a[k]
__newindexthe indexing assignment: a[k] = v
__callthe call operation: a(...)
__gcthe garbage-collection
__tostringthe convert to string operation: tostring(a)
__ipairsthe ipairs iterator: ipairs(a)
__pairsthe pairs iterator: pairs(a)
__existthe object existence checker
__fieldthe init object fields, must be a table
__newthe function used to generate the table that'd be converted to an object
__ctorthe object constructor
__dtorthe object destructor

Unlike the meta-data used by the PLoop. You must check the Lua version before using those meta-methods provided by the Lua.

static method

Besides the object method, the class can have its own static methods. The static methods can only be used by the class itself, also don't need use self as its first parameter.

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		local count = 0

		__Static__()
		function GetCount()
			return count
		end

		function __ctor(self, name)
			count = count + 1
			self.name = name
		end
	end)

	Ann = Person("Ann")
	Ben = Person("Ben")

	-- nil
	print(Ann.GetCount)

	-- 2
	print(Person.GetCount())
end)

The static method can only be accessed by the Person class, it also don't use self as its first argument.

We also could use the __Arguments__ attribute for the static method, unlike the object method, the type validation will start at the first argument.

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		local count = 0

		__Static__()
		__Arguments__{ String/"anonymous" }
		function New(name)
			return Person{ name = name }
		end
	end)

	Ann = Person.New("Ann")

	-- Ann
	print(Ann.name)
end)

Inheritance

The class can and only can have one super class, the class will inherit the super class's object methods, meta-data settings and other object features(like event, property).

If the class has override the super class's object method, meta-data or object feature, the class could use super keyword to access the super class's method, meta-data or object feature.

require "PLoop"

PLoop(function(_ENV)
	class "A" (function(_ENV)
		-- Object method
		function Test(self)
			print("Call A's method")
		end

		-- Constructor
		function __ctor(self)
			print("Call A's ctor")
		end

		-- Destructor
		function __dtor(self)
			print("Dispose A")
		end

		-- Meta-method
		function __call(self)
			print("Call A Object")
		end
	end)

	class "B" (function(_ENV)
		inherit "A"  -- also can use inherit(A)

		function Test(self)
			print("Call super's method ==>")
			super[self]:Test()
			super.Test(self)
			print("Call super's method ==<")
		end

		function __ctor(self)
			super(self)
			print("Call B's ctor")
		end

		function __dtor(self)
			print("Dispose B")
		end

		function __call(self)
			print("Call B Object")
			super[self]:__call()
			super.__call(self)
		end
	end)

	-- Call A's ctor
	-- Call B's ctor
	o = B()

	-- Call super's method ==>
	-- Call A's method
	-- Call A's method
	-- Call super's method ==<
	o:Test()

	-- Call B Object
	-- Call A Object
	-- Call A Object
	o()

	-- Dispose B
	-- Dispose A
	o:Dispose()
end)

From the example, here are some details:

  • The inherit "A" is a syntax sugar, is the same like inherit(A), since the environment can access the A by its name.

  • The destructor don't need call super's destructor, they are well controlled by the system, so the class only need to consider itself.

  • The constructor need call super's constructor manually, since only the class know what arguments should be used to call the super class's constructor.

  • For the object method and meta-method(include the __new and __exist, we have two style to call its super:

    • super.Test(self) is a simple version, it can only be used to call method or meta-method.

    • super[self]:Test() is the formal version, since the self is passed to super before access the Test method, the super'd know the class version of the object and used the correct version methods. This is used for multi-version classes(by default, re-define a class would create two different version), also for features like properties and events(we'll see them later).

  • The static methods won't be inherited.

The multi-version class

If we don't use the __Sealed__ to seal the classes, we still can re-define it, unlike the struct, redefine a class will only add or override the previous definition, not wipe them.

Take an example:

require "PLoop" (function(_ENV)
	class "A" (function(_ENV)
		function test(self)
			print("hi")
		end
	end)

	o = A()

	class "A" (function(_ENV)
		function test(self)
			print("hello")
		end
	end)

	o:test()   -- hi
	A():test() -- hello
end)

The old object won't receive the updating, so we have two version objects of the same class. It's designed to make sure the new definition won't break the old object(use some new fields don't existed in the old object and etc).

If we need a class whose object will receive all updatings, we must use the System.__SingleVer__ to mark it, so it'll always keep only one versions:

require "PLoop"

PLoop(function(_ENV)
	__SingleVer__()
	class "A" (function(_ENV)
		function test(self)
			print("hi")
		end
	end)

	o = A()

	class "A" (function(_ENV)
		function test(self)
			print("hello")
		end
	end)

	o:test()   -- hello
	A():test() -- hello
end)

So the old object will receive all updatings. If you need this to be default behaviors, you can modify the platform settings like :

PLOOP_PLATFORM_SETTINGS = { CLASS_NO_MULTI_VERSION_CLASS = true }

require "PLoop"

PLoop(function(_ENV)
	class "A" (function(_ENV)
		function test(self)
			print("hi")
		end
	end)

	o = A()

	class "A" (function(_ENV)
		function test(self)
			print("hello")
		end
	end)

	o:test()   -- hello
	A():test() -- hello
end)

Appending methods

There is another way to append methods without re-define the classes:

require "PLoop"

PLoop(function(_ENV)
	__Sealed__()
	class "A" (function(_ENV)
		function test(self)
			print("hi")
		end
	end)

	o = A()

	function A:test2()
		print("hello")
	end

	o:test2()   -- hello
end)

We can assign new object method or static method to the classes without a full re-definition. So, all object can receive the new method.

It also can be used on the sealed classes. Also we can use it on the interfaces.

We can't use this on the struct type, the method in a struct is copied to the data, if we add a method to a struct with no method, it'll change the struct from immutable to mutable, it's not allowed.

Interface

The interfaces are abstract types of functionality, it also provided the multi-inheritance mechanism to the class. Like the class, it support object method, static method and meta-datas.

The class and interface can extend many other interfaces, the super keyword'll choose the super object method, meta-method or object features based on the inheritance priority.

The interface use __init instead of the __ctor as the interface's initializer. The initializer only receive the object as it's parameter, and don't like the constructor, the initializer can't be accessed by super keyword. The method defined with the interface's name will also be used as the initializer.

If you only want defined methods and features that should be implemented by child interface or class, you can use __Abstract__ on the method or the feature, those abstract methods and featuers can't be accessed by super keyword.

Let's take an example :

require "PLoop"

PLoop(function(_ENV)
	interface "IName" (function(_ENV)
		__Abstract__()
		function SetName(self) end

		__Abstract__()
		function GetName(self) end

		-- initializer
		function __init(self) print("IName Init") end

		-- destructor
		function __dtor(self) print("IName Dispose") end
	end)

	interface "IAge" (function(_ENV)
		__Abstract__()
		function SetAge(self) end

		__Abstract__()
		function GetAge(self) end

		-- initializer
		function __init(self) print("IAge Init") end

		-- destructor
		function __dtor(self) print("IAge Dispose") end
	end)

	class "Person" (function(_ENV)
		extend "IName" "IAge"   -- also can use `extend(IName)(IAge)`

		-- Error: attempt to index global 'super' (a nil value)
		-- Since there is no super method(the IName.SetName is abstract),
		-- there is no super keyword can be use
		function SetName(self, name) super[self]:SetName(name) end

		function __ctor(self) print("Person Init") end

		function __dtor(self) print("Person Dispose") end
	end)

	-- Person Init
	-- IName Init
	-- IAge Init
	o = Person()

	-- IAge Dispose
	-- IName Dispose
	-- Person Dispose
	o:Dispose()
end)

Here are the details for the interface:

  • The initializers are called by the system after the object is created by the class, unlike the super class's constructor who is called by the class's constructor or used directly.

  • The initializers are called based on the extend orders, if an interface has extended another one, its initializer'll be called after its super interfaces.

  • The deconstruction order is the reversion of the construction. There is no need call super class or extended interfaces deconstructors manually.

  • The super keyword can't access abstract object method, abstract meta-method or abstract feature. The system will check if an interface or class could use the super keyword, remove the super could reduce cost of the object's accessing.

Anonymous class of interface

If we use System.__AnonymousClass__ attribute to an interface, the interface will create an anonymous class that extend itself, we can't use the anonymous class directly, but we can use the interface like we use a class:

require "PLoop"

PLoop(function(_ENV)
	__AnonymousClass__()
	interface "ITask" (function(_ENV)
		__Abstract__() function Process()
		end
	end)

	o = ITask{ Process = function() print("Hello") end }

	o:Process()
end)

The interface can only accept a table as the init-table to generate the object.

But for the interface with only one abstract method, we can use a simple style

require "PLoop"

PLoop(function(_ENV)
	__AnonymousClass__()
	interface "ITask" (function(_ENV)
		__Abstract__() function Process()
		end
	end)

	o = ITask(function() print("Hello") end)
	o:Process()
end)

We can pass a function as the implement of the abstract method to generate the object.

If you want all interface can be use as this, you can modify the platform settings(not recommend):

PLOOP_PLATFORM_SETTINGS = { INTERFACE_ALL_ANONYMOUS_CLASS = true }

require "PLoop"

PLoop(function(_ENV)
	interface "ITask" (function(_ENV)
		__Abstract__() function Process()
		end
	end)

	o = ITask(function() print("Hello") end)
	o:Process()
end)

the require class of the interface

We can use the require keyword to set a class to the interface, so all classes that extend the interface must be the sub-types of the class:

require "PLoop"

PLoop(function(_ENV)
	class "A" {}

	interface "IA" (function(_ENV)
		require "A"
	end)

	class "B" (function(_ENV)
		extend "IA" -- Error: interface.AddExtend(target, extendinterface[, stack]) - the class must be A's sub-class
	end)
end)

Event

The events are type features used to notify the outside that the state of class object has changed. Let's take an example to start :

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		-- declare an event for the class
		event "OnNameChanged"

		field { name = "anonymous" }

		function SetName(self, name)
			if name ~= self.name then
				-- Notify the outside
				OnNameChanged(self, name, self.name)
				self.name = name
			end
		end
	end)

	o = Person()

	-- Bind a function as handler to the event
	function o:OnNameChanged(new, old)
		print(("Renamed from %q to %q"):format(old, new))
	end

	-- Renamed from "anonymous" to "Ann"
	o:SetName("Ann")
end)

The event is a feature type for the class and interface, there are two types of the event handler :

  • the final handler - the previous example has shown how to bind the final handler.

  • the stackable handler - The stackable handler are normally used in the class's constructor or interface's initializer:

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		-- declare an event for the class
		event "OnNameChanged"

		field { name = "anonymous" }

		function SetName(self, name)
			if name ~= self.name then
				-- Notify the outside
				OnNameChanged(self, name, self.name)
				self.name = name
			end
		end
	end)

	class "Student" (function(_ENV)
		inherit "Person"

		local function onNameChanged(self, name, old)
			print(("Student %s renamed to %s"):format(old, name))
		end

		function Student(self, name)
			self:SetName(name)
			self.OnNameChanged = self.OnNameChanged + onNameChanged
		end
	end)

	o = Student("Ann")

	function o:OnNameChanged(name)
		print("My new name is " .. name)
	end

	-- Student Ann renamed to Ammy
	-- My new name is Ammy
	o:SetName("Ammy")
end)

The self.OnNameChanged is an object generated by the System.Delegate class who has __add and __sub meta-methods so it can works with the style like

self.OnNameChanged = self.OnNameChanged + onNameChanged

or

self.OnNameChanged = self.OnNameChanged - onNameChanged

The stackable handlers are added with orders, so the super class's handler'd be called at first then the class's, then the interface's. The final handler will be called at the last, if any handler return true, the call process will be ended.

In some scenarios, we need to block the object's event, the Delegate can set an init function that'd be called before all other handlers, we can use

self.OnNameChanged:SetInitFunction(function() return true end)

To block the object's OnNameChanged event.

The attribute for event handlers

When binding any event handlers, we can use attributes for them, but since the attribute may wrap the handler as a new function, you can't remove it like self.OnNameChanged = self.OnNameChanged - xxx.

require "PLoop" (function(_ENV)
	class "Person" (function(_ENV)
		event "OnNameChanged"

		function SetName(self, name)
			self.name = name
			OnNameChanged(self, name)
		end
	end)

	ann = Person()

	__Async__()
	function ann:OnNameChanged(new)
		print("Final:", coroutine.running(), new)
	end

	__Async__()
	ann.OnNameChanged = ann.OnNameChanged + function(self, new)
		print("Stack:", coroutine.running(), new)
	end

	-- Stack:    thread: 00B90ECC    Ann
	-- Final:    thread: 00B90ECC    Ann
	ann:SetName("Ann")
end)

The event of the event handler changes

When using PLoop to wrap objects generated from other system, we may need to bind the PLoop event to other system's event, there is two parts in it :

  • When the PLoop object event handlers are changed, we need know when and whether there is any handler for that event, so we can register or un-register in the other system.

  • When the event of the other system is triggered, we need invoke the PLoop's event.

Take the Frame widget from the World of Warcraft as an example, ignore the other details, let's focus on the event two-way binding :

require "PLoop"

PLoop(function(_ENV)
	class "Frame" (function(_ENV)
		__EventChangeHandler__(function(delegate, owner, eventname, init)
			-- delegate is the object whose handlers are changed
			-- owner is the frame object, also the owner of the delegate
			-- eventname is the OnEnter for this case
			-- init is only provided when the delegate created with true value
			if delegate:IsEmpty() then
				-- No event handler, so un-register the frame's script event
				owner:SetScript(eventname, nil)
			else
				-- Has event handler, so we must regiser the frame's script event
				if owner:GetScript(eventname) == nil then
					owner:SetScript(eventname, function(self, ...)
						-- Call the delegate directly
						delegate(owner, ...)
					end)
				end
			end
		end)
		event "OnEnter"
	end)
end)

With the __EventChangeHandler__ attribute, we can bind a function to the target event, so all changes of the event handlers can be checked in the function. Since the event change handler has nothing special with the target event, we can use it on all script events in one system like :

require "PLoop"

PLoop(function(_ENV)
	local function changehandler (delegate, owner, eventname)
		if delegate:IsEmpty() then
			owner:SetScript(eventname, nil)
		else
			if owner:GetScript(eventname) == nil then
				owner:SetScript(eventname, function(self, ...)
					-- Call the delegate directly
					delegate(owner, ...)
				end)
			end
		end
	end

	function __WidgetEvent__(self)
		__EventChangeHandler__(changehandler)
	end

	class "Frame" (function(_ENV)
		__WidgetEvent__()
		event "OnEnter"

		__WidgetEvent__()
		event "OnLeave"
	end)
end)

Static event

The event can also be marked as static, so it can be used and only be used by the class or interface :

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		__Static__()
		event "OnPersonCreated"

		function Person(self, name)
			OnPersonCreated(name)
		end
	end)

	function Person.OnPersonCreated(name)
		print("Person created " .. name)
	end

	-- Person created Ann
	o = Person("Ann")
end)

super event

When the class or interface has overridden the event, and they need register handler to super event, we can use the super object access style :

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		-- declare an event for the class
		event "OnNameChanged"

		field { name = "anonymous" }

		function SetName(self, name)
			if name ~= self.name then
				-- Notify the outside
				OnNameChanged(self, name, self.name)
				self.name = name
			end
		end
	end)


	class "Student" (function(_ENV)
		inherit "Person"

		event "OnNameChanged"

		local function raiseEvent(self, ...)
			OnNameChanged(self, ...)
		end

		function Student(self)
			super(self)

			-- Use the super object access style
			super[self].OnNameChanged = raiseEvent
		end
	end)

	o = Student()

	function o:OnNameChanged(name)
		print("New name is " .. name)
	end

	-- New name is Test
	o:SetName("Test")
end)

As we can see, the child class can listen the super's event and then raise its own event.

Property

The properties are object states, we can use the table fields to act as the object states, but they lack the value validation, and we also can't track the modification of those fields.

Like the event, the property is also a feature type for the interface and class. The property system provide many mechanisms like get/set, value type validation, value changed handler, value changed event, default value and default value factory. Let's start with a simple example :

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		property "Name" { type = String }
		property "Age"  { type = Number }
	end)

	o = Person{ Name = "Ann", Age = 10 }

	print(o.Name)-- Ann
	o.Name = 123 -- Error : the Name must be string, got number
end)

The Person class has two properties: Name and Age, the table after property "Name" is the definition of the Name property, it contains a type field that contains the property value's type, so when we assign a number value to the Name, the operation is failed.

Like the member of the struct, we use table to give the property's definition, the key is case ignored, here is a full list:

FieldUsage
autowhether use the auto-binding mechanism for the property see blow example for details.
getthe function used to get the property value from the object like get(obj), also you can set false to it, so the property can't be read
setthe function used to set the property value of the object like set(obj, value), also you can set false to it, so the property can't be written
getmethodthe string name used to specified the object method to get the value like obj[getmethod](obj)
setmethodthe string name used to specified the object method to set the value like obj[setmethod](obj, value)
fieldthe table field to save the property value, no use if get/set specified, like the Name of the Person, since there is no get/set or field specified, the system will auto generate a field for it, it's recommended.
typethe value's type, if the value is immutable, the type validation can be turn off for release version, just turn on TYPE_VALIDATION_DISABLED in the PLOOP_PLATFORM_SETTINGS
defaultthe default value, must match the type setting
factorythe default value factory, used to generate the default value of the property
eventthe event used to handle the property value changes, if it's value is string, an event will be created
handlerthe function used to handle the property value changes, unlike the event, the handler is used to notify the class or interface itself, normally this is used combine with field (or auto-gen field), so the class or interface only need to act based on the value changes
statictrue if the property is a static property
indexertrue if the property is an indexer property
throwabletrue if the property's set method'll throw errors
requiretrue if can't use nil as value

We'll see examples for each case:

get/set

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		field { __name = "anonymous" }

		property "Name" {
			get = function(self) return self.__name end,
			set = function(self, name) self.__name = name end,
		}
	end)

	print(Person().Name)
end)

getmethod/setmethod

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		field { __name = "anonymous" }

		function SetName(self, name)
			self.__name = name
		end

		function GetName(self)
			return self.__name
		end

		property "Name" {
			get = "GetName", -- or getmethod = "GetName"
			set = "SetName", -- or setmethod = "SetName"
		}
	end)

	print(Person().Name)
end)

property-throw

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		field { __name = "anonymous" }

		function SetName(self, name)
			if type(name) ~= "string" then
				throw("The name must be string")
			end
			self.__name = name
		end

		function GetName(self)
			return self.__name
		end

		property "Name" {
			get = "GetName", -- or getmethod = "GetName"
			set = "SetName", -- or setmethod = "SetName"
			throwable = true,
		}
	end)

	Person().Name = 123 -- Error: The name must be string
end)

field & default

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		property "Name" { field = "__name", default = "anonymous" }
		property "Age"  { type = Number } -- the field'll be auto generated
	end)

	obj = Person()
	print(obj.Name, obj.__name) -- anonymous   nil
	obj.Name = "Ann"
	obj.Age  = 24

	-- __name	Ann
	-- _Person_Age	24
	for k, v in pairs(obj) do
		print(k, v)
	end
end)

default factory

If your property type is not function type, you can use default instead of the factory, it's the same.

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		property "Age" { field = "__age", factory = function(self) return math.random(100) end }
	end)

	obj = Person()
	print(obj.Age, obj.__age) -- 81   81
	obj.Age = nil   -- so the factory will works again
	print(obj.Age, obj.__age) -- 88   88
end)

property-event

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		property "Name" { type = String, event = "OnNameChanged" }
	end)

	o = Person { Name = "Ann" }

	function o:OnNameChanged(new, old, prop)
		print(("[%s] %s -> %s"):format(prop, old, new))
	end

	-- [Name] Ann -> Ammy
	o.Name = "Ammy"
end)

property-handler

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		property "Name" {
			type = String, default = "anonymous",

			handler = function(self, new, old, prop)
				print(("[%s] %s -> %s"):format(prop, old, new))
			end
		}
	end)

	--[Name] anonymous -> Ann
	o = Person { Name = "Ann" }

	--[Name] Ann -> Ammy
	o.Name = "Ammy"
end)

static property

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		__Static__()
		property "DefaultName" { type = String }

		property "Name" {
			type = String, default = function() return Person.DefaultName end,
		}
	end)

	Person.DefaultName = "noname"

	print(Person().Name) -- noname
end)

Auto-binding

If using the auto-binding mechanism and the definition don't provide get/set, getmethod/setmethod and field, the system will check the property owner's method(object method if non-static, static method if it is static), take an example if our property name is "name":

  • The setname, Setname, SetName, setName will be scanned, if it existed, the method will be used as the set setting

  • The getname, Getname, Isname, isname, getName, GetName, IsName, isname will be scanned, if it exsited, the method will be used as the get setting

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		function SetName(self, name)
			print("SetName", name)
		end

		property "Name" { type = String, auto = true }
	end)

	-- SetName  Ann
	o = Person { Name = "Ann"}

	-- SetName  Ammy
	o.Name = "Ammy"
end)

super property

When the class or interface has overridden the property, they still can use the super object access style to use the super's property :

require "PLoop"

PLoop(function(_ENV)
	class "Person" (function(_ENV)
		property "Name" { type = String }
	end)

	class "Student" (function(_ENV)
		inherit "Person"

		property "Name" {
			Set = function(self, name)
				-- Use super property to save
				super[self].Name = name
			end,
			Get = function(self)
				-- Use super property to fetch
				return super[self].Name
			end,
		}
	end)

	o = Student()
	o.Name = "Test"
	print(o.Name)   -- Test
end)

indexer property

We also can build indexer properties like :

require "PLoop"

PLoop(function(_ENV)
	class "A" (function( _ENV )
		__Indexer__()
		property "Items" {
			set = function(self, idx, value)
				self[idx] = value
			end,
			get = function(self, idx)
				return self[idx]
			end,
			type = String,
		}
	end)

	o = A()

	o.Items[1] = "Hello"

	print(o.Items[1])   -- Hello
end)

The indexer property can only accept set, get, getmethod, setmethod, type and static definitions.

We also can give a type for the indexer property's key:

require "PLoop"

PLoop(function(_ENV)
	class "A" (function( _ENV )
		__Indexer__(String)
		property "Items" {
			set = function(self, idx, value)
				self[idx] = value
			end,
			get = function(self, idx)
				return self[idx]
			end,
			type = String,
		}
	end)

	o = A()

	-- Error: the Items's key must be string, got number
	o.Items[1] = "Hello"
end)

Get/Set Modifier

Beside those settings, we still can provide some behavior modifiers on the properties.

For property set, we have System.PropertySet to describe the value set behavior:

__Flags__() __Default__(0)
enum "System.PropertySet" {
	Assign      = 0,  -- assign directly
	Clone       = 1,  -- save the clone of the value
	DeepClone   = 2,  -- save the deep clone of the value
	Retain      = 4,  -- should dispose the old value
	Weak        = 8,  -- save the value as weak mode
}

For property get, we have System.PropertyGet to describe the value get behavior:

__Flags__() __Default__(0)
enum "System.PropertyGet" {
	Origin      = 0,  -- return the value directly
	Clone       = 1,  -- return a clone of the value
	DeepClone   = 2,  -- return a deep clone of the value
}

To apply them on the property, we need System.__Set__ and System.__Get__ attributes:

require "PLoop"

PLoop(function(_ENV)
	class "Data" (function(_ENV)
		extend "ICloneable"  -- cloneable class must extend this interface

		local _Cnt = 0

		-- Implement the Clone method
		function Clone(self)
			return Data() -- for test, just return a new one
		end

		function Dispose(self)
			print("Dispose Data " .. self.Index)
		end

		function __ctor(self)
			_Cnt = _Cnt + 1
			self.Index = _Cnt
		end
	end)

	class "A" (function(_ENV)
		__Set__(PropertySet.Clone + PropertySet.Retain)
		__Get__(PropertySet.Clone)
		property "Data" { type = Data }
	end)

	o = A()

	dt = Data()

	o.Data = dt
	print(dt.Index, o.Data.Index)  -- 1  3
	o.Data = nil   -- Dispose Data 2
end)

Init only

We can declare a property to be init-only, so it can only be set in the constructor:

PLOOP_PLATFORM_SETTINGS = { MULTI_OS_THREAD = false }

require "PLoop" (function(_ENV)
    class "A" (function(_ENV)
        property "Name" { init = true }

        function __ctor(self, name)
            self.Name = name
        end
    end)

    o = A("Ann")
    o.Name = 123 -- lua: xxxxx.lua:13: the Name is init-only
end)

Inheritance and Priority

A class can extend many interfaces and inherit one super class who'll have its own extended interfaces and super class.

If those extend interfaces and super classes have the same name feature(method, property, event, meta-method), the system will choose the nearest:

  • check the super class, then the super class's super class, repeat until no more super classes.

  • check the interfaces, the latest extended interface should be checked first.

Those are done by the system, so we don't need to control it, but we may affect it by give priority to those features with the System.__Abstract__ and System.__Final__ attributes:

  • If a feature(method, meta-method, property or event) marked with __Abstract__, the feature's priority is the lowest.

  • If a feature marked with __Final__, the feature's priority is the highest.

Here is an example:

require "PLoop"

PLoop(function(_ENV)
	interface "IA" (function(_ENV)
		__Final__()
		function Test(self)
			print("Hello IA")
		end

		__Abstract__()
		function Test2(self)
			print("Hello2 IA")
		end
	end)

	class "A" (function(_ENV)
		extend "IA"

		function Test(self)
			print("Hello A")
		end

		function Test2(self)
			print("Hello2 A")
		end
	end)

	o = A()
	o:Test()  -- Hello IA
	o:Test2() -- Hello2 A
end)

There is also a special usage of the __Final__ attribute, we can define the final method in the interface or abstract class, and call the object's own method with a trick:

require "PLoop"

PLoop(function(_ENV)
	interface "IA" (function(_ENV)
		__Final__() function Test(self)
			print("Call Test of IA")

			-- get the object's class, use the normal Test method of the class to do the real job
			Class.GetNormalMethod(Class.GetObjectClass(self), "Test")(self)
		end
	end)

	class "A" { IA, Test = function(self) print("Call Test of A") end }

	o = A()

	-- Call Test of IA
	-- Call Test of A
	o:Test()
end)

The object will use the final method, but the class'll keep its own version.

Use other definition style

Use string as definition body

The struct, interface and class can use string as the definition body, it's very simple, just have one example:

PLoop(function(_ENV)
	class "A" [[
		property "Name" { default = "anonymous" }
	]]

	print(A().Name)
end)

Just replace the function(_ENV) and end) to the start and end of the string.

Use table as definition body

We already see Table Style Definition for the struct, it's also possible to define interface or class with tables:

require "PLoop"

PLoop(function(_ENV)
	class "Person" {
		-- Declare a static event
		-- it's not good to use the event in table
		OnPersonCreated = true,

		-- Declare an object event
		OnNameChanged   = false,

		-- Define property, we can use type
		-- or a table for the property
		Name = String,
		Age  = { type = Number, default = 0 },

		-- Define a object method
		SetName = function(self, name)
			self:OnNameChanged(name, self.Name)
			self.Name = name
		end,

		-- Declare the constructor, we also can use
		-- `__ctor` as the key
		function (self, name)
			Person.OnPersonCreated(name)
			self.Name = name
		end,
	}

	interface "IScore" {
		Person, -- if the type is class, means require it
		ICloneable,  -- if the type is interface, means extend it
	}

	class "Student" {
		Person, -- if the type is class, means inherit it
		IScore, -- if the type is interface, means extend it
	}

	-- We can declare the method later
	function Student:SetScore(score)
	end
end)

Template class and interface

Like the template struct, we also can create template class or interface:

require "PLoop"

PLoop(function(_ENV)
	__Arguments__ { AnyType }
	class "Array" (function(_ENV, eletype)
		__Arguments__{ eletype * 0 }
		function __new(cls, ...)
			return { ... }, true
		end
	end)

	--Error: Usage: Anonymous([... as System.Integer]) - the 4th argument must be number, got string
	o = Array[Integer](1, 2, 3, "hi", 5)
end)

Also we can inherit or extend the template class or interface:

require "PLoop"

PLoop(function(_ENV)
	__Arguments__ { AnyType }
	class "Array" (function(_ENV, eletype)
		__Arguments__{ eletype * 0 }
		function __new(cls, ...)
			return { ... }, true
		end
	end)

	class "NumberArray" { Array[Number] }

	__Arguments__ { AnyType } (AnyType)
	class "Queue" (function(_ENV, eletype)
		inherit (Array[eletype])
	end)
end)

System.Interface

The System.Interface is the proxy created from the interface prototype. It contains all features of the interface system. (The APIs used by System won't be introduced, but you can find them easily in the Prototype.lua.)

GetExtends

Get all the extended interfaces of the target interface

  • Params:
    • target - the target interface
  • Return:
    • iter - function, the iterator
    • target - the target interface

GetFeature

Get a type feature of the target interface

  • Params:
    • target - the target interface
    • name - string, the feature's name
    • forobject - boolean, whether get from the object feature
  • Return:
    • type - the array element's type

GetFeatures

Get all the features of the target interface

  • Params:
    • target - the target interface
    • forobject - boolean, whether get from the object feature
  • Return:
    • iter - function, the iterator
    • target - the target interface

GetMethod

Get a method of the target interface

  • Params:
    • target - the target interface
    • name - string, the method's name
    • forobject - boolean, whether get from the object method
  • Return:
    • method - the method
    • isstatic - boolean, true if this is a static method

GetMethods

Get all the methods of the interface

  • Params:
    • target - the target interface
    • forobject - boolean, whether get from the object method
  • Return:
    • iter - function, the iterator
    • target - the target interface

GetMetaMethod

Get a meta-method of the target interface

  • Params:
    • target - the target interface
    • name - string, the meta-method's name
    • forobject - boolean, whether get from the object meta-methods
  • Return:
    • method - the method

GetMetaMethods

Get all the meta-methods of the interface

  • Params:
    • target - the target interface
    • forobject - boolean, whether get from the object meta-methods
  • Return:
    • iter - function, the iterator
    • target - the target interface

GetRequireClass

Get the require class of the target interface

  • Params:
    • target - the target interface
  • Return: class - the require class

GetSuperMethod

Get the super method of the target interface with the given name

  • Params:
    • target - the target interface
    • name - string, the method's name
  • Return:
    • method - function, the super method

GetSuperMetaMethod

Get the super meta-method of the target interface with the given name

  • Params:
    • target - the target interface
    • name - string, the meta-method's name
  • Return:
    • method - function, the super meta-method

GetSuperFeature

Get the super feature of the target interface with the given name

  • Params:
    • target - the target interface
    • name - string, the feature's name
  • Return:
    • feature - the super feature

GetSuperRefer

Get the super refer of the target interface

  • Params:
    • target - the target interface
  • Return:
    • super - the super refer

GetTemplate

Get the template interface

  • Params:
    • target - the target interface
  • Return:
    • template - the template interface

GetTemplateParameters

Get the template parameters

  • Params:
    • target - the target interface
  • Return:
    • ... - the paramter list of the template

HasAnonymousClass

Whether the interface has anonymous class

  • Params:
    • target - the target interface
  • Return:
    • flag - true if the interface has anonymous class

IsAbstract

Whether the interface's method, meta-method or feature is abstract

  • Params:
    • target - the target interface
    • name - the method, meta-method, feature name
  • Return:
    • flag - true if it abstract

IsFinal

Whether the interface or its method, meta-method, feature is final

  • Params:
    • target - the target interface
    • name - the method, meta-method, feature name
  • Return:
    • flag - true if it final

IsImmutable

The objects are always immutable for type validation

  • Params:
    • target - the target interface
  • Return:
    • flag - always true
    • isAlways - always true

IsSealed

Whether the interface is sealed, can't be re-defined

  • Params:
    • target - the target interface
  • Return:
    • flag - true if the interface is sealed

IsStaticMethod

Whether the interface's given name method is static

  • Params:
    • target - the target interface
    • name - the method name
  • Return:
    • flag - true if the method is static

IsSubType

Whether the target interface is a sub-type of another interface

  • Params:
    • target - the target interface
    • extendIF - the extened interface
  • Return:
    • flag - true if the target interface is a sub-type of another interface

ValidateValue

Validate the value with an interface

  • Format: (target, value[, onlyvalid])
  • Params:
    • target - the target interface
    • value - the value
    • onlyvalid - true if only validate the value, no value modifiy
  • Return:
    • value - the validated value
    • errormsg - the error message if not pass

Validate

Whether the value is an interface type

  • Params:
    • target - the target
  • Return
    • target - nil if not pass the validation

System.Class

The System.Class is the proxy created from the class prototype. It contains all features of the class system. (The APIs used by System won't be introduced, but you can find them easily in the Prototype.lua.)

AttachObjectSource

Attach source place to the object

  • Format: (object[, stack])
  • Params:
    • object - the target object
    • stack - the stack level
  • Return:
    • object - the target object

GetExtends

Get all the extended interfaces of the target class

  • Params:
    • target - the target class
  • Return:
    • iter - function, the iterator
    • target - the target class

GetFeature

Get a type feature of the target class

  • Params:
    • target - the target class
    • name - string, the feature's name
    • forobject - boolean, whether get from the object feature
  • Return:
    • type - the array element's type

GetFeatures

Get all the features of the target class

  • Params:
    • target - the target class
    • forobject - boolean, whether get from the object feature
  • Return:
    • iter - function, the iterator
    • target - the target class

GetMethod

Get a method of the target class

  • Params:
    • target - the target class
    • name - string, the method's name
    • forobject - boolean, whether get from the object method
  • Return:
    • method - the method
    • isstatic - boolean, true if this is a static method

GetMethods

Get all the methods of the class

  • Params:
    • target - the target class
    • forobject - boolean, whether get from the object method
  • Return:
    • iter - function, the iterator
    • target - the target class

GetMetaMethod

Get a meta-method of the target class

  • Params:
    • target - the target class
    • name - string, the meta-method's name
    • forobject - boolean, whether get from the object meta-methods
  • Return:
    • method - the method

GetMetaMethods

Get all the meta-methods of the class

  • Params:
    • target - the target class
    • forobject - boolean, whether get from the object meta-methods
  • Return:
    • iter - function, the iterator
    • target - the target class

GetNormalMethod

Get the normal method of the target with the given name

  • Params:
    • target - the target class
    • name - the given name
  • Return:
    • function - the normal method

GetNormalMetaMethod

Get the normal method of the target with the given name

  • Params:
    • target - the target class
    • name - the given name
  • Return:
    • function - the normal meta-method

GetNormalFeature

Get the normal method of the target with the given name

  • Params:
    • target - the target class
    • name - the given name
  • Return:
    • feature - the normal feature

GetObjectClass

Get the object class of the object

  • Params:
    • target - the target object
  • Return:
    • class - the object class

GetObjectSource

Get the object's creation place

  • Params:
    • target - the target object
  • Return:
    • source - where the object is created

GetSuperClass

Get the super class of the target class

  • Params:
    • target - the target class
  • Return:
    • super - the super class

GetSuperMethod

Get the super method of the target class with the given name

  • Params:
    • target - the target class
    • name - string, the method's name
  • Return:
    • method - function, the super method

GetSuperMetaMethod

Get the super meta-method of the target class with the given name

  • Params:
    • target - the target class
    • name - string, the meta-method's name
  • Return:
    • method - function, the super meta-method

GetSuperFeature

Get the super feature of the target class with the given name

  • Params:
    • target - the target class
    • name - string, the feature's name
  • Return:
    • feature - the super feature

GetSuperObjectStyle

Whether the class use super object access style like super[obj].Name = "Ann"

  • Params:
    • target - the target class
  • Return:
    • usesuper - true if the class don't use super object access style

GetSuperRefer

Get the super refer of the target class

  • Params:
    • target - the target class
  • Return:
    • super - the super refer

GetTemplate

Get the template class

  • Params:
    • target - the target class
  • Return:
    • template - the template class

GetTemplateParameters

Get the template parameters

  • Params:
    • target - the target class
  • Return:
    • ... - the paramter list of the template

IsAbstract

Whether the class or its method, meta-method or feature is abstract

  • Format: (target[, name])
  • Params:
    • target - the target class
    • name - the method, meta-method, feature name
  • Return:
    • flag - true if it abstract

IsFinal

Whether the class's method, meta-method, feature is final

  • Params:
    • target - the target class
    • name - the method, meta-method, feature name
  • Return:
    • flag - true if it final

IsImmutable

The objects are always immutable for type validation

  • Params:
    • target - the target class
  • Return:
    • flag - always true
    • isAlways - always true

IsObjectAttributeEnabled

Whether the attributes can be applied on the class's objects

  • Params:
    • target - the target class
  • Return:
    • flag - true if the attributes can be applied on the class's objects

IsObjectFunctionAttributeEnabled

Whether the class object has enabled the attribute for functions will be defined in it

  • Params:
    • target - the target class
  • Return:
    • flag - true if the class object enabled the function attribute

IsObjectSourceDebug

Whether the class object'll save its source when created

  • Params:
    • target - the target class
  • Return:
    • flag - true if the class object'll save its source when created

IsObjectType

Whether the object is generated from the target type

  • Params:
    • target - the target object
    • type - the interface or class
  • Return:
    • flag - true if the object is generated from the target type

IsMethodAutoCache

Whether the class object will try to auto cache the object methods

  • Params:
    • target - the target class
  • Return:
    • flag - true if the class object will try to auto cache the object methods

IsNilValueBlocked

Whether the class object don't receive any value assignment excpet existed fields

  • Params:
    • target - the target class
  • Return:
    • flag - true if the class object don't receive any value assignment excpet existed fields

IsRawSetBlocked

Whether the class object don't receive any value assignment excpet existed fields

  • Params:
    • target - the target class
  • Return:
    • flag - true if the class object don't receive any value assignment excpet existed fields

IsSealed

Whether the class is sealed, can't be re-defined

  • Params:
    • target - the target class
  • Return:
    • flag - true if the class is sealed

IsSingleVersion

Whether the class is a single version class, so old object would receive re-defined class's features

  • Params:
    • target - the target class
  • Return:
    • flag - true if the class is a single version class

IsStaticMethod

Whether the class's given name method is static

  • Params:
    • target - the target class
    • name - the method name
  • Return:
    • flag - true if the method is static

IsSubType

Whether the target class is a sub-type of another interface|class

  • Format: (target, extendIF) (target, supercls)
  • Params:
    • target - the target class
    • extendIF - the extened interface
    • supercls - the super class
  • Return:
    • flag - true if the target class is a sub-type of another interface or class

ValidateValue

Validate the value with a class

  • Format: (target, value[, onlyvalid])
  • Params:
    • target - the target class
    • value - the value
    • onlyvalid - true if only validate the value, no value modifiy
  • Return:
    • value - the validated value
    • errormsg - the error message if not pass

Validate

Whether the value is a class type

  • Params:
    • target - the target
  • Return
    • target - nil if not pass the validation

System.Event

The System.Event is the proxy created from the event prototype. It contains all features of the event system. (The APIs used by System won't be introduced, but you can find them easily in the Prototype.lua.)

IsStatic

Whether the event is static

  • Params:
    • target - the event
  • Return
    • flag - true if it's a static event

Invoke

Invoke an event with parameters

  • Format: (target[, object], ...)
  • Params:
    • target - the event
    • object - the target object
    • ... - the parameters

Validate

Whether the target is an event

  • Params:
    • target - the target
  • Return:
    • target - the target if it's an event

As an example:

require "PLoop"(function(_ENV)
	class "A" (function(_ENV)
		__Static__() event "OnObjectCreated"

		event "OnNameChanged"
	end)

	for name, feature in Class.GetFeatures(A) do
		if Event.Validate(feature) then
			print(name, Event.IsStatic(feature))
		end
	end
end)

System.Property

The System.Property is the proxy created from the property prototype. It contains all features of the property system. (The APIs used by System won't be introduced, but you can find them easily in the Prototype.lua.)

GetField

Get the property field if existed

  • Params:
    • target - the property
  • Return
    • string - the property field

IsGetClone

Whether the property should return a clone copy of the value

  • Params:
    • target - the property
  • Return
    • flag - true if should return a clone copy of the value

IsGetDeepClone

Whether the property should return a deep clone copy of the value

  • Params:
    • target - the property
  • Return
    • flag - true if should return a deep clone copy of the value

IsIndexer

Whether the property is an indexer property, used like obj.prop[xxx] = xxx

  • Params:
    • target - the property
  • Return
    • flag - true if the property is an indexer

IsReadable

Whether the property is readable

  • Params:
    • target - the property
  • Return
    • flag - true if the property is readable

IsSetClone

Whether the property should save a clone copy to the value

  • Params:
    • target - the property
  • Return
    • flag - true if should save a clone copy to the value

IsSetDeepClone

Whether the property should save a deep clone copy to the value

  • Params:
    • target - the property
  • Return
    • flag - true if should save a deep clone copy to the value

IsRetainObject

Whether the property should dispose the old value

  • Params:
    • target - the property
  • Return
    • flag - true if should dispose the old value

IsStatic

Whether the property is static

  • Params:
    • target - the property
  • Return
    • flag - true if the property is static

IsThrowable

Whether the property is throwable

  • Params:
    • target - the property
  • Return
    • flag - true if the property is throwable

IsWeak

Whether the property value should kept in a weak table

  • Params:
    • target - the property
  • Return
    • flag - true if the property value should kept in a weak table

IsWritable

Whether the property is writable

  • Params:
    • target - the property
  • Return
    • flag - true if the property is writable

GetDefault

Get the property default value

  • Params:
    • target - the property
  • Return
    • default - the default value

GetType

Get the property type

  • Params:
    • target - the property
  • Return
    • type - the property value type

Validate

Wether the value is a property

  • Params:
    • target - the property
  • Return
    • target - return the taret is it's a property