Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Class implementation #2

Open
AshleyJamesy opened this issue Jul 24, 2019 · 1 comment
Open

Class implementation #2

AshleyJamesy opened this issue Jul 24, 2019 · 1 comment
Labels
enhancement New feature or request

Comments

@AshleyJamesy
Copy link

AshleyJamesy commented Jul 24, 2019

Hey I wrote a class module I use for my project that you might like to implememt. There is some functions missing such as being able to call upper class functions, but feel free to look at the code. It has saved me a lot of time when needing to create classes.

It's very simple to use, all you need to do is have a dedicated folder for classes ie; classes and call

files = love.filesystem.getDirectoryItems("classes/")

for k, v in pairs(files) do
	class.Load("classes/" .. v)
end

class.Build()

at the very start of main.lua

Example of an entity class might look like this:

class.Name = "Entity"

function class:New(name)
	self.name = name
	self.health = 100
end

function class:Foo()
	print("Foo")
end

you can also inherit by using class.Base

class.Name = "Player"
class.Base = "Entity"

you can also choose to override functions or leave them

class.Name = "Player"
class.Base = "Entity"

--overrides new
function class:New(name)
	class:Base(self, name)
	
	self.xp = 100
end

--overrides entity foo (no way to call base class function yet)
function class:Foo()
	print("Bar")
end

you instantiate like so

local bob = Player("Bob")
bob:Foo()

you can also easily check what type of class something is

local bob = Player("Bob")
print(bob:Type()) --returns 'Player'
print(bob:IsType("Entity")) --returns true if Player class inherits from Entity

You can head here to see the full extent of what I've done with classes:

https://github.com/AshleyJamesy/Lua/tree/master/Projects/UI2/library/Engine/Classes

it uses an old design but still works about the same

Script:

module("class", package.seeall)

local classes = {}

local meta_script = { 
	__index = _G, 
	__newindex = _G
}

local function inherit(n, b)
	for k, v in pairs(b) do
		if rawget(n, k) then
		else
			rawset(n, k, v)
		end
	end
end

local function LoadLuaFile(path, env)
	local info = love.filesystem.getInfo(path, "file")
	
	if info then
	local contents, size = love.filesystem.read(path)
	if contents then
		env = env or {}
		setmetatable(env, meta_script)
		
		local chunk, err = loadstring(contents)
		
		if not chunk then
			return 2, "compile error: " .. err
		else
			setfenv(chunk, env)
			
			local success, err = pcall(chunk)
			if not success then
				return 3, "compile error: " .. err
			end
		end
	end
	else
		return 1, "does not exist"
	end
	
	return 0
end

local base = {
	__call = function(t, ...)
		local instance = setmetatable({}, t)
		
		return instance:New(...) or instance
	end
}

function Load(path)
	for k, v in pairs(classes) do
		if path == v.path then
			print(path .. " - " .. "compile error: <" .. v.class.Name .. "> already loaded")
			return
		end
	end
	
	local env = {
		class = {},
		script = {},
		GetClass = function(name)
			return class[name]
		end
	}
	
	local status, err = LoadLuaFile(path, env)
	
	if status ~= 0 then
		console.error(path .. " - " .. err)
		return        
	end
	
	local class = setmetatable(env.class, base)
	
	if type(class.Name) ~= "string" then
		if type(class.Name) == "nil" then
			print(path .. " - " .. "compile error: missing class name")
		else
			print(path .. " - " .. "compile error: class name must be string")
		end
		
		return
	else
		if not string.match(class.Name, "^[a-zA-Z]+[0-9]*$") then
			if class.Name:find("%s") then
				print(path .. " - " .. "compile error: class name must be alphanumeric without whitespaces")
				return
			end
			
			print(path .. " - " .. "compile error: class name must start with alpha character")
			return
		end
	
		if classes[class.Name] then
			print(path .. " - " .. "compile error: <" .. class.Name .. "> already exists")
			return
		end
	end
	
	if class.Type then
		print(path .. " - " .. "compile error: <" .. class.Name .. "> 'Type' is a reserved keyword")
		return
	end
	
	if class.Base then
		if type(class.Base) ~= "string" then
			print(path .. " - " .. "compile error: base class name must be string")
			return
		else
			if not string.match(class.Base, "^[a-zA-Z]+[0-9]*$") then
				if class.Base:find("%s") then
					print(path .. " - " .. "compile error: base class name must be alphanumeric without whitespaces")
					return
				end
			
				print(path .. " - " .. "compile error: base class name must start with alpha character")
				return
			end
		end
	end
	
	local classData = {}
	
	classData.Name = class.Name
	classData.Base = class.Base
	classData.class = class
	classData.path = path
	classData.loaded = false
	classData.types = { class.Name }
		
	class.__index = class
	
	class.__call = function(self, ...)
		if class.__copy then
			return setmetatable(class.__copy(self), class)
		else
			print(path .. " - " .. "runtime error: <" .. class.Name .. "> missing '__copy' constructor")
		end
	end
	
	class.Base = function(class, self, ...)
		return classData.baseclass.New(self, ...)
	end
	
	class.Type = function()
		return classData.types[1]
	end
	
	class.IsType = function(self, type)
		for k, v in pairs(classData.types) do
			if v == type then
				return true
			end
		end
		
		return false
	end
	
	classes[classData.Name] = classData
end

local function buildClass(c)
	if c.loaded then
	else
		local b = classes[c.Base]
		
		if c.Base == nil then
			if type(c.class.New) ~= "function" then
				if type(c.class.New) == "nil" then
					print(c.path .. " - " .. "compile error: <" .. c.Name .. "> missing 'New' constructor")
				else
					print(c.path .. " - " .. "compile error: <" .. c.Name .. "> 'New' constructor must be function")
				end
		
				return
			end
			
			c.loaded = true
			
			_G[c.Name] = c.class
			
			return
		end
		
		if b then
			if not b.loaded then
				 buildClass(b)  
			end
			
			if b.loaded then
				inherit(c.class, b.class)
				
				if type(c.class.New) ~= "function" then
					if type(c.class.New) == "nil" then
						print(c.path .. " - " .. "compile error: <" .. c.class.Name .. "> missing 'New' constructor")
					else
						print(c.path .. " - " .. "compile error: <" .. c.class.Name .. "> 'New' constructor must be function")
					end
		
					return
				end
				
				c.loaded = true
				c.baseclass = b.class
				
				for k, v in pairs(b.types) do
					table.insert(c.types, #c.types + 1, v)
				end
				
				_G[c.Name] = c.class
			end
		else
			print(c.path .. " - build error: <" .. c.Base .. "> missing")
		end
	end
end

function Build()
	for k, v in pairs(classes) do
		buildClass(v)
	end
end
@Zakru
Copy link
Owner

Zakru commented Jul 24, 2019

Thanks, I'll keep this in mind. For now a lot of the code is very makeshift, but after polishing it I'll make sure to take a look at this.

@Zakru Zakru added the enhancement New feature or request label Jul 24, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants