A module/mixin system written in the Lua programming language.
A module can be thought of as a unit (of code), which is used to facilitate a more complex purpose (our program). Lua doesn't naturally come pre-built with the idea of a class
, however it offers the power of metatables
to imitate inheritance. This idea is the main idea behind Modern
, but with a bit more.
Inheritance - any modules can extend any other module.
Mixins - extend your modules beyond their ability without affecting the inheritance chain. Functions with the same name will compound into one function call.
Utility Functions - check out the API
Direct Download
- Download the latest release from Modern's release page.
- Unpack and upload to a folder that is recognized by
LUA_PATH
.
LuaRocks
luarocks install modern
- Simply include
modern
within a new file.
local Modern = require 'modern'
- Extend from
Modern
to create a fresh module, inheriting all it's functionality.
local Player = Modern:extend()
- Now you can add additional functionality to your module.
-- `new` automatically runs when Module is called
function Player:new(x, y)
self.x = x
self.y = y
end
Modern
allows you to create polymorphic relationships with other Modules
.
local Modern = require 'modern'
local Enemy = Modern:extend() -- inherits everything from `Modern`
local Orc = Enemy:extend() -- inherits everything from `Enemy`
local Troll = Enemy:extend() -- inherits everything from `Enemy`
Mixins
are added as arguments when calling extend
. You can add another Module
or a basic table
as an argument. Any functions with conflicting names will compound so that they are all fired in sequence when called.
local Modern = require 'modern'
local AABB = require 'mixins.AABB'
local FSM = require 'mixins.FSM'
local Enemy = Modern:extend(AABB, FSM)
A use case for using Mixins
would be adding a Finite State Machine to your Module
(in this case Enemy
). It doesn't make sense to inherit from FSM
, but we want to include the functionality to update our Enemy
states each game cycle. By adding FSM
as a mixin expands the base Module
's functionality.
Every Module
is provided a name & namespace upon creation (__call()
and extend()
). The __name
is just the variable name you assigned the Module
, while the __namespace
can be thought of as it's inheritance path. Here's an example:
local Modern = require 'modern'
local Enemy = Modern:extend()
local Troll = Enemy:extend()
print(Troll.__name) -- prints "Troll"
print(Troll.__namespace) -- prints "Modern\Enemy\Troll"
In this example we create a simple enemy hierarchy. Notice the call to the parent's new
function: self.__super.new(self, x, y)
. If not called, the parent's new
would be skipped. Our Gnome
module sets it's own attack power, which will override the attack
value from 5
to 10
.
local Modern = require 'modern'
--
local Enemy = Modern:extend()
function Enemy:new(x, y)
self.x = x
self.y = y
print('Enemy:new', x, y)
end
--
local Gnome = Enemy:extend()
function Gnome:new(x, y, attack)
self.__super.new(self, x, y) -- call parent `new`
self.attack = attack
print(self.__name .. ':new', x, y, attack)
end
function Gnome:strike()
print(self.__name .. ' strikes for ' .. self.attack)
end
Running the code...
$ lua
> gnome = Gnome(70, 80, 10) # Enemy:new 70, 80
# Gnome:new 70, 80, 10
> print(gnome.x, gnome.y) # 70, 80
> print(gnome.attack) # 10
> gnome:strike() # Gnome strikes for 10
In this (silly) example we'll show an example using mixins and how conflicting function names are handled.
local Modern = require 'modern'
--
local M1 = Modern:extend()
function M1:new() print('M1:new') end
function M1:foo() print('M1:foo') end
--
local M2 = Modern:extend()
function M2:new() print('M2:new') end
function M2:foo() print('M2:foo') end
--
local MM = Modern:extend(M1, M2)
function MM:new() print('MM:new') end
function MM:foo() print('MM:foo') end
Running the code...
$ lua
> mm = MM() # MM:new
# M1:new
# M2:new
> mm:foo() # MM:foo
# M1:foo
# M2:foo
Notice how all 3 foo
functions are called (in order of inclusion).
Important: All
Mixins
and their functions must be declared before adding them via theextend
function. This is due to how Lua's__index
and__newindex
metamethods work (see here).
Love2D is a fantastic framework to get you up and running with graphics, audio, and easy window configurations. This example shows how to use Modern
to draw multiple layers using Mixins
.
First we'll create a Player
module including an AABB
module, which provides axis-aligned bounding box functionality for collision, and in our example, debugging.
-- player.lua
local Modern = require 'modern'
--
local AABB = Modern:extend()
function AABB:new()
-- using `Player` variables to create some more
self.left = self.x
self.top = self.y
self.right = self.x + self.width
self.bottom = self.y + self.height
end
-- ...
-- Really cool, useful functions removed for brevity :p
-- ...
function AABB:draw()
if self.debug then
love.graphics.setColor(1, 0, 0, 1)
love.graphics.draw(self.image, self.x, self.y, self.width, self.height)
end
end
--
local Player = Modern:extend(AABB)
function Player:new(x, y, src)
local image = love.graphics.newImage(src)
local w, h = image:getDimensions( )
self.x = x
self.y = y
self.image = image
self.scale = 0.5
self.width = w * self.scale
self.height = h * self.scale
self.debug = false
end
function Player:draw()
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(self.image, self:center())
end
return Player
Next, using Love2D we draw the player
instance.
-- main.lua
local Player = require 'player'
local player
function love.load()
player = Player(50, 50, 'player.png')
player.debug = true
end
function love.draw()
player:draw()
end
Finally, our reward!
__call
- Create new Module
instance.
is(obj)
- Checks if Module
is a (or inherits from)...
has(obj)
- Checks Module
for inclusion of a Mixin
.
super(obj)
- Fetch super Module
of current Module
.
copy()
- shallow copy (using rawset
) of Module
.
clone()
- Deep copy (including metatables
) of Module
.
extend(...)
- Extend from another Module
inheriting all it's goodies.
__tostring()
- Visualization of Module
showing properties.
$ lua
> print(MyExampleModule)
# ---------------------------------------------------------------------------
# | [ ] | Module | Namespace | DataType | Key | Value |
---------------------------------------------------------------------------
# | [-] | Orc | Modern\Enemy\Orc | number | attack | 100 |
# | [-] | Orc | Modern\Enemy\Orc | function | new | <function> |
# | [-] | Orc | Modern\Enemy\Orc | number | y | 2 |
# | [-] | Orc | Modern\Enemy\Orc | number | x | 1 |
# | [-] | Orc | Modern\Enemy\Orc | number | health | 100 |
# | [^] | Enemy | Modern\Enemy | function | new | <function> |
# | [-] | Modern | Modern | function | extend | <function> |
# | [-] | Modern | Modern | function | copy | <function> |
# | [-] | Modern | Modern | function | super | <function> |
# | [-] | Modern | Modern | function | clone | <function> |
# | [-] | Modern | Modern | function | has | <function> |
# | [-] | Modern | Modern | function | is | <function> |
# | [+] | Health | Modern\Health | function | new | <function> |
# | [+] | Health | Modern\Health | function | heal | <function> |
# | [+] | Health | Modern\Health | function | hit | <function> |
# | [+] | Combat | Modern\Combat | function | defend | <function> |
# | [+] | Combat | Modern\Combat | function | new | <function> |
# | [+] | Combat | Modern\Combat | function | hit | <function> |
# ---------------------------------------------------------------------------
Note:
[-]
normal property,[^]
- overridden property,[+]
- mixin function
This project is licensed under the MIT License - see the LICENSE.md file for details