Triggering and listening to an event - matlab

I have a class that represent some data and a (short) chain of results originating from this data. The class has the three properties Data1, Data2 and Data3, where Data2 depends on Data1 (it is actually a filtered version of it) and Data3 depends on Data2.
My idea was to trigger an event when Data1 changes that computes Data2, and then trigger an other event that leads to the computation of Data3. However, in the test class below, Data3 is empty after construction.
Note that I use uiw.event.EventData from the Widget toolbox.
classdef test < handle
events
DataChanged
end
properties
DataChangedListener event.listener
end
properties (AbortSet)
Data1 (:,:) double = []
Data2 (:,:) double = []
Data3 (:,:) double = []
end
methods
function self = test()
self.DataChangedListener = event.listener(self, 'DataChanged', #self.onDataChanged);
self.Data1 = peaks(64);
end
%% setter
function set.Data1(self, d)
self.Data1 = d;
evt = uiw.event.EventData( ...
'EventType', 'DataChanged', ...
'Property', 'Data1', ...
'Model', self );
self.notify('DataChanged', evt);
end
function set.Data2(self, d)
self.Data2 = d;
evt = uiw.event.EventData( ...
'EventType', 'DataChanged', ...
'Property', 'Data2', ...
'Model', self );
self.notify('DataChanged', evt);
end
function set.Data3(self, d)
self.Data3 = d;
evt = uiw.event.EventData( ...
'EventType', 'DataChanged', ...
'Property', 'Data3', ...
'Model', self );
self.notify('DataChanged', evt);
end
%% Event callback
function onDataChanged(self, ~, evt)
switch evt.Property
case 'Data1'
self.Data2 = self.Data1 + 5;
case 'Data2'
self.Data3 = self.Data2 + 10;
end
end
end
end
If one debugs this, one sees that onDataChanged is never called with the uiw.event.eventData with evt.Property = 'Data2'.
Das anyone know why and how to trigger the event properly?

I think I solved the puzzle.
Your code executes the listener callback recursively.
By default recursive listener is disabled.
Refer to Event and Listener Concepts:
Recursive — Allow listener to trigger the same event that caused execution of the callback.
Recursive is false by default. If the callback triggers the event for which it is defined as the callback, the listener cannot execute recursively. Therefore, set Recursive to false if the callback must trigger its own event. Setting the Recursive property to true can create a situation where infinite recursion reaches the recursion limit and triggers an error.
All you need to do is setting Recursive property to true:
After self.DataChangedListener = event.listener(self, 'DataChanged', #self.onDataChanged);
Add: self.DataChangedListener.Recursive = true;

Related

whats the difference between class and instance?

whats the difference between class and instance in lua?
I know classes are like the template and the instance is the object created from the template but I am wondering what the difference in code is.
my goal is to make a system that works like this..
--class.widget.lua----------------------------------------------------------
local class = require "class"
local widget = class("class.widget")
function widget:create()
--create variables here
end
function widget:update()
--update variables here
end
function widget:render()
--widget render
end
return widget
--class.widget.button.lua---------------------------------------------------
local class = require "class"
local widget = require "class.widget"
local button = class("button", widget)
function button:create(x, y)
base:create()
--create variables here
end
function button:update()
base:update()
--update variables here
end
function button:render()
base:render()
--widget render
end
return button
--main.lua------------------------------------------------------------------
local button = require "class.widget.button.lua"
button1 = button(0, 0)
button2 = button(0, 16)
even though this hasn't been answered, this right here is working exactly the way I want it to
I am posting it here if anybody wants to use it
EDIT: this is a better version for both me and any one looking for a good lua class
return function(name, base)
local class = {}
--private vars
class.__name = name
class.__base = base
class.__index = class
class.__event = "create"
--events
class.onCreate = function(inst) end
class.onUpdate = function(inst) end
class.onRender = function(inst) end
class.onDelete = function(inst) end
--functions
class.create = function(inst) inst.__event = "create" inst:onCreate() end
class.update = function(inst) inst.__event = "update" inst:onUpdate() end
class.render = function(inst) inst.__event = "render" inst:onRender() end
class.delete = function(inst) inst.__event = "delete" inst:onDelete() end
class.getBase = function(inst) return inst.__base end
class.getName = function(inst) return inst.__name end
class.inheritEvent = function(inst)
if inst.__event == "create" then inst.__base:create() end
if inst.__event == "update" then inst.__base:update() end
if inst.__event == "render" then inst.__base:render() end
if inst.__event == "delete" then inst.__base:delete() end
end
--constructor
local MT = {}
MT.__index = base
function MT:__call(_, ...)
local inst = setmetatable({}, class)
inst:create(inst, ...)
return inst
end
return setmetatable(class, MT)
end

how to access a gui object in a nested function from a base workspace

I think my issue is I don't know how to access guiel.hAX(3) property in my button callback function where I put kiddies = get(guiel.hAX(3),'Children');
I got the error:
Undefined variable "guiel" or class "guiel.hAX".
Error in showTF/callback_update_model (line 508)
kiddies = get(guiel.hAX(3),'Children');
My nested function for button callback:
function callback_update_model(~,~)
vars.dropheight = str2num(get(edit(2),'String'));
vars.armradius = str2num(get(edit(1),'String'));
kiddies = get(guiel.hAX(3),'Children');
delete(kiddies);
clear kiddies;
set(guiel.tfPanel,'Visible','off','Position',cnst.tfPanelpos);
set(guiel.hAX(1),'Position',cnst.axpos1);
if ishandle(guiel.hAX(2))
set(guiel.hAX(2),'Position',cnst.axpos2);
end
eval(get(guiel.hPB(4),'Callback'));
end
I initialize variables in other mfile
guiel.hAX(1) = -1;
guiel.hAX(2) = -1;
guiel.hAX(3) = -1;
guiel.tfPanel = -1;
...
guiel.hAX(3) = axes('Parent',guiel.tfPanel,'Color',cnst.OFFWHITE,'Layer',...
'top','Xlim',[0 1],'YLim',[0 1],'GridLineStyle','none','Units','Normalized',...
'XTick',[],'YTick',[],'Box','off','Visible','off','Position',cnst.axpos3);
To make data available in callbacks, you can store the data in the UserData property of the figure. If your figure handle is h_fig, then you would use code like this to store the data in the figure:
set(h_fig, 'UserData', guiel)
In your callback, you can use the gcbo function to get the handle to the figure, then extract the user data, use code like this:
[~, h_fig] = gcbo;
guiel = get(h_fig, 'UserData')

Drag and drop files in Matlab GUI

I am trying to find a way to use drag and drop in my matlab GUI. The closest I've found is this.
However, I would like the result to look like this:
When a file has been dropped, all I need is the path of the file and a call to my load function.
All suggestions are much appreciated!
This submission by Maarten van der Seijs on the file exchange seems to solve it.
It creates a callback function which can be coupled to java swing GUI components, as shown in the attached demo.
It uses a java class, which is a thin wrapper around java.awt.dnd.DropTarget:
import java.awt.dnd.*;
import java.awt.datatransfer.*;
import java.util.*;
import java.io.File;
import java.io.IOException;
public class MLDropTarget extends DropTarget
{
/**
* Modified DropTarget to be used for drag & drop in MATLAB UI control.
*/
private static final long serialVersionUID = 1L;
private int droptype;
private Transferable t;
private String[] transferData;
public static final int DROPERROR = 0;
public static final int DROPTEXTTYPE = 1;
public static final int DROPFILETYPE = 2;
#SuppressWarnings("unchecked")
#Override
public synchronized void drop(DropTargetDropEvent evt) {
// Make sure drop is accepted
evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
// Set droptype to zero
droptype = DROPERROR;
// Get transferable and analyze
t = evt.getTransferable();
try {
if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
// Interpret as list of files
List<File> fileList = (ArrayList<File>) t.getTransferData(DataFlavor.javaFileListFlavor);
transferData = new String[fileList.size()];
for (int i = 0; i < fileList.size(); i++)
transferData[i] = fileList.get(i).getAbsolutePath();
droptype = DROPFILETYPE;
}
else if (t.isDataFlavorSupported(DataFlavor.stringFlavor)) {
// Interpret as string
transferData[0] = (String) t.getTransferData(DataFlavor.stringFlavor);
droptype = DROPTEXTTYPE;
}
} catch (UnsupportedFlavorException e) {
droptype = DROPERROR;
super.drop(evt);
return;
} catch (IOException e) {
droptype = DROPERROR;
super.drop(evt);
return;
}
// Call built-in drop method (fire MATLAB Callback)
super.drop(evt);
}
public int getDropType() {
return droptype;
}
public Transferable getTransferable() {
return t;
}
public String[] getTransferData() {
return transferData;
}
}
which is then initialized and called by a MATLAB class:
classdef (CaseInsensitiveProperties) dndcontrol < handle
%DNDCONTROL Class for Drag & Drop functionality.
% obj = DNDCONTROL(javaobj) creates a dndcontrol object for the specified
% Java object, such as 'javax.swing.JTextArea' or 'javax.swing.JList'. Two
% callback functions are available: obj.DropFileFcn and obj.DropStringFcn,
% that listen to drop actions of respectively system files or plain text.
%
% The Drag & Drop control class relies on a Java class that need to be
% visible on the Java classpath. To initialize, call the static method
% dndcontrol.initJava(). The Java class can be adjusted and recompiled if
% desired.
%
% DNDCONTROL Properties:
% Parent - The associated Java object.
% DropFileFcn - Callback function for system files.
% DropStringFcn - Callback function for plain text.
%
% DNDCONTROL Methods:
% dndcontrol - Constructs the DNDCONTROL object.
%
% DNDCONTROL Static Methods:
% defaultDropFcn - Default callback function for drop events.
% demo - Runs the demonstration script.
% initJava - Initializes the Java class.
% isInitialized - Checks if the Java class is visible.
%
% A demonstration is available from the static method dndcontrol.demo().
%
% Example:
% dndcontrol.initJava();
% dndcontrol.demo();
%
% See also:
% uicontrol, javaObjectEDT.
%
% Written by: Maarten van der Seijs, 2015.
% Version: 1.0, 13 October 2015.
properties (Hidden)
dropTarget;
end
properties (Dependent)
%PARENT The associated Java object.
Parent;
end
properties
%DROPFILEFCN Callback function executed upon dropping of system files.
DropFileFcn;
%DROPSTRINGFCN Callback function executed upon dropping of plain text.
DropStringFcn;
end
methods (Static)
function initJava()
%INITJAVA Initializes the required Java class.
%Add java folder to javaclasspath if necessary
if ~dndcontrol.isInitialized();
classpath = fileparts(mfilename('fullpath'));
javaclasspath(classpath);
end
end
function TF = isInitialized()
%ISINITIALIZED Returns true if the Java class is initialized.
TF = (exist('MLDropTarget','class') == 8);
end
end
methods
function obj = dndcontrol(Parent,DropFileFcn,DropStringFcn)
%DNDCONTROL Drag & Drop control constructor.
% obj = DNDCONTROL(javaobj) contstructs a DNDCONTROL object for
% the given parent control javaobj. The parent control should be a
% subclass of java.awt.Component, such as most Java Swing widgets.
%
% obj = DNDCONTROL(javaobj,DropFileFcn,DropStringFcn) sets the
% callback functions for dropping of files and text.
% Check for Java class
assert(dndcontrol.isInitialized(),'Javaclass MLDropTarget not found. Call dndcontrol.initJava() for initialization.')
% Construct DropTarget
obj.dropTarget = handle(javaObjectEDT('MLDropTarget'),'CallbackProperties');
set(obj.dropTarget,'DropCallback',{#dndcontrol.DndCallback,obj});
set(obj.dropTarget,'DragEnterCallback',{#dndcontrol.DndCallback,obj});
% Set DropTarget to Parent
if nargin >=1, Parent.setDropTarget(obj.dropTarget); end
% Set callback functions
if nargin >=2, obj.DropFileFcn = DropFileFcn; end
if nargin >=3, obj.DropStringFcn = DropStringFcn; end
end
function set.Parent(obj, Parent)
if isempty(Parent)
obj.dropTarget.setComponent([]);
return
end
if isa(Parent,'handle') && ismethod(Parent,'java')
Parent = Parent.java;
end
assert(isa(Parent,'java.awt.Component'),'Parent is not a subclass of java.awt.Component.')
assert(ismethod(Parent,'setDropTarget'),'DropTarget cannot be set on this object.')
obj.dropTarget.setComponent(Parent);
end
function Parent = get.Parent(obj)
Parent = obj.dropTarget.getComponent();
end
end
methods (Static, Hidden = true)
%% Callback functions
function DndCallback(jSource,jEvent,obj)
if jEvent.isa('java.awt.dnd.DropTargetDropEvent')
% Drop event
try
switch jSource.getDropType()
case 0
% No success.
case 1
% String dropped.
string = char(jSource.getTransferData());
if ~isempty(obj.DropStringFcn)
evt = struct();
evt.DropType = 'string';
evt.Data = string;
feval(obj.DropStringFcn,obj,evt);
end
case 2
% File dropped.
files = cell(jSource.getTransferData());
if ~isempty(obj.DropFileFcn)
evt = struct();
evt.DropType = 'file';
evt.Data = files;
feval(obj.DropFileFcn,obj,evt);
end
end
% Set dropComplete
jEvent.dropComplete(true);
catch ME
% Set dropComplete
jEvent.dropComplete(true);
rethrow(ME)
end
elseif jEvent.isa('java.awt.dnd.DropTargetDragEvent')
% Drag event
action = java.awt.dnd.DnDConstants.ACTION_COPY;
jEvent.acceptDrag(action);
end
end
end
methods (Static)
function defaultDropFcn(src,evt)
%DEFAULTDROPFCN Default drop callback.
% DEFAULTDROPFCN(src,evt) accepts the following arguments:
% src - The dndcontrol object.
% evt - A structure with fields 'DropType' and 'Data'.
fprintf('Drop event from %s component:\n',char(src.Parent.class()));
switch evt.DropType
case 'file'
fprintf('Dropped files:\n');
for n = 1:numel(evt.Data)
fprintf('%d %s\n',n,evt.Data{n});
end
case 'string'
fprintf('Dropped text:\n%s\n',evt.Data);
end
end
function [dndobj,hFig] = demo()
%DEMO Demonstration of the dndcontrol class functionality.
% dndcontrol.demo() runs the demonstration. Make sure that the
% Java class is visible in the Java classpath.
% Initialize Java class
dndcontrol.initJava();
% Create figure
hFig = figure();
% Create Java Swing JTextArea
jTextArea = javaObjectEDT('javax.swing.JTextArea', ...
sprintf('Drop some files or text content here.\n\n'));
% Create Java Swing JScrollPane
jScrollPane = javaObjectEDT('javax.swing.JScrollPane', jTextArea);
jScrollPane.setVerticalScrollBarPolicy(jScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
% Add Scrollpane to figure
[~,hContainer] = javacomponent(jScrollPane,[],hFig);
set(hContainer,'Units','normalized','Position',[0 0 1 1]);
% Create dndcontrol for the JTextArea object
dndobj = dndcontrol(jTextArea);
% Set Drop callback functions
dndobj.DropFileFcn = #demoDropFcn;
dndobj.DropStringFcn = #demoDropFcn;
% Callback function
function demoDropFcn(~,evt)
switch evt.DropType
case 'file'
jTextArea.append(sprintf('Dropped files:\n'));
for n = 1:numel(evt.Data)
jTextArea.append(sprintf('%d %s\n',n,evt.Data{n}));
end
case 'string'
jTextArea.append(sprintf('Dropped text:\n%s\n',evt.Data));
end
jTextArea.append(sprintf('\n'));
end
end
end
end

Using System.Reactive deserialize messages

I currently have a program that listens to a network stream and fires events when a new message has been deserialized.
while(true)
{
byte[] lengthBytes = new byte[10];
networkStream.Read(lengthBytes, 0, 10);
int messageLength = Int32.Parse(Encoding.UTF8.GetString(lengthBytes));
var messageBytes = new byte[messageLength + 10];
Array.Copy(lengthBytes, messageBytes, 10);
int bytesReadTotal = 10;
while (bytesReadTotal < 10 + messageLength)
bytesReadTotal += networkStream.Read(messageBytes, bytesReadTotal, messageLength - bytesReadTotal + 10);
OnNewMessage(new MessageEventArgs(messageFactory.GetMessage(messageBytes)));
}
I want to rewrite this using the reactive extensions so that instead of the event there is an IObservable<Message>. This could be done using
Observable.FromEvent<EventHandler<MessageEventArgs>, MessageEventArgs>(
(h) => NewMessage += h,
(h) => NewMessage -= h)
.Select( (e) => { return e.Message; });
However I would prefer to rewrite the listening process using System.Reactive instead. My starting point (from here) is
Func<byte[], int, int, IObservable<int>> read;
read = Observable.FromAsyncPattern<byte[], int, int, int>(
networkStream.BeginRead,
networkStream.EndRead);
which allows
byte[] lengthBytes = new byte[10];
read(lengthBytes, 0, lengthBytes.Length).Subscribe(
{
(bytesRead) => ;
});
I'm struggling to see how to continue though. Does anyone have an implementation?
I came up with the following, but I feel it should be possible without creating a class and using Subject<T> (e.g. via some projection of the header packet to the body packet to the message object, but the problem with that is EndRead() doesn't return the byte array, but the number of bytes read. So you need an object or atleast a closure at some point).
class Message
{
public string Text { get; set; }
}
class MessageStream : IObservable<Message>
{
private readonly Subject<Message> messages = new Subject<Message>();
public void Start()
{
// Get your real network stream here.
var stream = Console.OpenStandardInput();
GetNextMessage( stream );
}
private void GetNextMessage(Stream stream)
{
var header = new byte[10];
var read = Observable.FromAsyncPattern<byte [], int, int, int>( stream.BeginRead, stream.EndRead );
read( header, 0, 10 ).Subscribe( b =>
{
var bodyLength = BitConverter.ToInt32( header, 0 );
var body = new byte[bodyLength];
read( body, 0, bodyLength ).Subscribe( b2 =>
{
var message = new Message() {Text = Encoding.UTF8.GetString( body )};
messages.OnNext( message );
GetNextMessage( stream );
} );
} );
}
public IDisposable Subscribe( IObserver<Message> observer )
{
return messages.Subscribe( observer );
}
}
Since Observable.FromAsyncPattern only makes the async call once, you will need to make a function that will call it multiple times instead. This should get you started, but probably has lots of room for improvement. It assumes that you can make the async calls repeatedly with the same arguments and assumes that the selector will handle any issues that arise from this.
Function FromRepeatedAsyncPattern(Of T1, T2, T3, TCallResult, TResult)(
begin As Func(Of T1, T2, T3, AsyncCallback, Object, IAsyncResult),
[end] As Func(Of IAsyncResult, TCallResult),
selector As Func(Of TCallResult, TResult),
isComplete As Func(Of TCallResult, Boolean)
) As Func(Of T1, T2, T3, IObservable(Of TResult))
Return Function(a1, a2, a3) Observable.Create(Of TResult)(
Function(obs)
Dim serial As New SerialDisposable()
Dim fac = Observable.FromAsyncPattern(begin, [end])
Dim onNext As Action(Of TCallResult) = Nothing
'this function will restart the subscription and will be
'called every time a value is found
Dim subscribe As Func(Of IDisposable) =
Function()
'note that we are REUSING the arguments, the
'selector should handle this appropriately
Return fac(a1, a2, a3).Subscribe(onNext,
Sub(ex)
obs.OnError(ex)
serial.Dispose()
End Sub)
End Function
'set up the OnNext handler to restart the observer
'every time it completes
onNext = Sub(v)
obs.OnNext(selector(v))
'subscriber disposed, do not check for completion
'or resubscribe
If serial.IsDisposed Then Exit Sub
If isComplete(v) Then
obs.OnCompleted()
serial.Dispose()
Else
'using the scheduler lets the OnNext complete before
'making the next async call.
'you could parameterize the scheduler, but it may not be
'helpful, and it won't work if Immediate is passed.
Scheduler.CurrentThread.Schedule(Sub() serial.Disposable = subscribe())
End If
End Sub
'start the first subscription
serial.Disposable = subscribe()
Return serial
End Function)
End Function
From here, you can get an IObservable(Of Byte) like so:
Dim buffer(4096 - 1) As Byte
Dim obsFac = FromRepeatedAsyncPattern(Of Byte(), Integer, Integer, Integer, Byte())(
AddressOf stream.BeginRead, AddressOf stream.EndRead,
Function(numRead)
If numRead < 0 Then Throw New ArgumentException("Invalid number read")
Console.WriteLine("Position after read: " & stream.Position.ToString())
Dim ret(numRead - 1) As Byte
Array.Copy(buffer, ret, numRead)
Return ret
End Function,
Function(numRead) numRead <= 0)
'this will be an observable of the chunk size you specify
Dim obs = obsFac(buffer, 0, buffer.Length)
From there, you will need some sort of accumulator function that takes byte arrays and outputs complete messages when they are found. The skeleton of such a function might look like:
Public Function Accumulate(source As IObservable(Of Byte())) As IObservable(Of Message)
Return Observable.Create(Of message)(
Function(obs)
Dim accumulator As New List(Of Byte)
Return source.Subscribe(
Sub(buffer)
'do some logic to build a packet here
accumulator.AddRange(buffer)
If True Then
obs.OnNext(New message())
'reset accumulator
End If
End Sub,
AddressOf obs.OnError,
AddressOf obs.OnCompleted)
End Function)
End Function

How to create a class, subclass and properties in Lua?

I'm having a hard time grokking classes in Lua. Fruitless googling led me to ideas about meta-tables, and implied that third-party libraries are necessary to simulate/write classes.
Here's a sample (just because I've noticed I get better answers when I provide sample code):
public class ElectronicDevice
{
protected bool _isOn;
public bool IsOn { get { return _isOn; } set { _isOn = value; } }
public void Reboot(){_isOn = false; ResetHardware();_isOn = true; }
}
public class Router : ElectronicDevice
{
}
public class Modem :ElectronicDevice
{
public void WarDialNeighborhood(string areaCode)
{
ElectronicDevice cisco = new Router();
cisco.Reboot();
Reboot();
if (_isOn)
StartDialing(areaCode);
}
}
Here is my first attempt to translate the above using the technique suggested by Javier.
I took the advice of RBerteig. However, invocations on derived classes still yield: "attempt to call method 'methodName' (a nil value)"
--Everything is a table
ElectronicDevice = {};
--Magic happens
mt = {__index=ElectronicDevice};
--This must be a constructor
function ElectronicDeviceFactory ()
-- Seems that the metatable holds the fields
return setmetatable ({isOn=true}, mt)
end
-- Simulate properties with get/set functions
function ElectronicDevice:getIsOn() return self.isOn end
function ElectronicDevice:setIsOn(value) self.isOn = value end
function ElectronicDevice:Reboot() self.isOn = false;
self:ResetHardware(); self.isOn = true; end
function ElectronicDevice:ResetHardware() print('resetting hardware...') end
Router = {};
mt_for_router = {__index=Router}
--Router inherits from ElectronicDevice
Router = setmetatable({},{__index=ElectronicDevice});
--Constructor for subclass, not sure if metatable is supposed to be different
function RouterFactory ()
return setmetatable ({},mt_for_router)
end
Modem ={};
mt_for_modem = {__index=Modem}
--Modem inherits from ElectronicDevice
Modem = setmetatable({},{__index=ElectronicDevice});
--Constructor for subclass, not sure if metatable is supposed to be different
function ModemFactory ()
return setmetatable ({},mt_for_modem)
end
function Modem:WarDialNeighborhood(areaCode)
cisco = RouterFactory();
--polymorphism
cisco.Reboot(); --Call reboot on a router
self.Reboot(); --Call reboot on a modem
if (self.isOn) then self:StartDialing(areaCode) end;
end
function Modem:StartDialing(areaCode)
print('now dialing all numbers in ' .. areaCode);
end
testDevice = ElectronicDeviceFactory();
print("The device is on? " .. (testDevice:getIsOn() and "yes" or "no") );
testDevice:Reboot(); --Ok
testRouter = RouterFactory();
testRouter:ResetHardware(); -- nil value
testModem = ModemFactory();
testModem:StartDialing('123'); -- nil value
Here's an example literal transcription of your code, with a helpful Class library that could be moved to another file.
This is by no means a canonical implementation of Class; feel free to define your object model however you like.
Class = {}
function Class:new(super)
local class, metatable, properties = {}, {}, {}
class.metatable = metatable
class.properties = properties
function metatable:__index(key)
local prop = properties[key]
if prop then
return prop.get(self)
elseif class[key] ~= nil then
return class[key]
elseif super then
return super.metatable.__index(self, key)
else
return nil
end
end
function metatable:__newindex(key, value)
local prop = properties[key]
if prop then
return prop.set(self, value)
elseif super then
return super.metatable.__newindex(self, key, value)
else
rawset(self, key, value)
end
end
function class:new(...)
local obj = setmetatable({}, self.metatable)
if obj.__new then
obj:__new(...)
end
return obj
end
return class
end
ElectronicDevice = Class:new()
function ElectronicDevice:__new()
self.isOn = false
end
ElectronicDevice.properties.isOn = {}
function ElectronicDevice.properties.isOn:get()
return self._isOn
end
function ElectronicDevice.properties.isOn:set(value)
self._isOn = value
end
function ElectronicDevice:Reboot()
self._isOn = false
self:ResetHardware()
self._isOn = true
end
Router = Class:new(ElectronicDevice)
Modem = Class:new(ElectronicDevice)
function Modem:WarDialNeighborhood(areaCode)
local cisco = Router:new()
cisco:Reboot()
self:Reboot()
if self._isOn then
self:StartDialing(areaCode)
end
end
If you were to stick to get/set methods for properties, you wouldn't need __index and __newindex functions, and could just have an __index table. In that case, the easiest way to simulate inheritance is something like this:
BaseClass = {}
BaseClass.index = {}
BaseClass.metatable = {__index = BaseClass.index}
DerivedClass = {}
DerivedClass.index = setmetatable({}, {__index = BaseClass.index})
DerivedClass.metatable = {__index = DerivedClass.index}
In other words, the derived class's __index table "inherits" the base class's __index table. This works because Lua, when delegating to an __index table, effectively repeats the lookup on it, so the __index table's metamethods are invoked.
Also, be wary about calling obj.Method(...) vs obj:Method(...). obj:Method(...) is syntactic sugar for obj.Method(obj, ...), and mixing up the two calls can produce unusual errors.
There are a number of ways you can do it but this is how I do (updated with a shot at inheritance):
function newRGB(r, g, b)
local rgb={
red = r;
green = g;
blue = b;
setRed = function(self, r)
self.red = r;
end;
setGreen = function(self, g)
self.green= g;
end;
setBlue = function(self, b)
self.blue= b;
end;
show = function(self)
print("red=",self.red," blue=",self.blue," green=",self.green);
end;
}
return rgb;
end
purple = newRGB(128, 0, 128);
purple:show();
purple:setRed(180);
purple:show();
---// Does this count as inheritance?
function newNamedRGB(name, r, g, b)
local nrgb = newRGB(r, g, b);
nrgb.__index = nrgb; ---// who is self?
nrgb.setName = function(self, n)
self.name = n;
end;
nrgb.show = function(self)
print(name,": red=",self.red," blue=",self.blue," green=",self.green);
end;
return nrgb;
end
orange = newNamedRGB("orange", 180, 180, 0);
orange:show();
orange:setGreen(128);
orange:show();
I don't implement private, protected, etc. although it is possible.
If you don't want to reinvent the wheel, there is a nice Lua library implementing several object models. It's called LOOP.
The way I liked to do it was by implementing a clone() function.
Note that this is for Lua 5.0. I think 5.1 has more built-in object oriented constructions.
clone = function(object, ...)
local ret = {}
-- clone base class
if type(object)=="table" then
for k,v in pairs(object) do
if type(v) == "table" then
v = clone(v)
end
-- don't clone functions, just inherit them
if type(v) ~= "function" then
-- mix in other objects.
ret[k] = v
end
end
end
-- set metatable to object
setmetatable(ret, { __index = object })
-- mix in tables
for _,class in ipairs(arg) do
for k,v in pairs(class) do
if type(v) == "table" then
v = clone(v)
end
-- mix in v.
ret[k] = v
end
end
return ret
end
You then define a class as a table:
Thing = {
a = 1,
b = 2,
foo = function(self, x)
print("total = ", self.a + self.b + x)
end
}
To instantiate it or to derive from it, you use clone() and you can override things by passing them in another table (or tables) as mix-ins
myThing = clone(Thing, { a = 5, b = 10 })
To call, you use the syntax :
myThing:foo(100);
That will print:
total = 115
To derive a sub-class, you basically define another prototype object:
BigThing = clone(Thing, {
-- and override stuff.
foo = function(self, x)
print("hello");
end
}
This method is REALLY simple, possibly too simple, but it worked well for my project.
It's really easy to do class-like OOP in Lua; just put all the 'methods' in the __index field of a metatable:
local myClassMethods = {}
local my_mt = {__index=myClassMethods}
function myClassMethods:func1 (x, y)
-- Do anything
self.x = x + y
self.y = y - x
end
............
function myClass ()
return setmetatable ({x=0,y=0}, my_mt)
Personally, I've never needed inheritance, so the above is enough for me. If it's not enough, you can set a metatable for the methods table:
local mySubClassMethods = setmetatable ({}, {__index=myClassMethods})
local my_mt = {__index=mySubClassMethods}
function mySubClassMethods:func2 (....)
-- Whatever
end
function mySubClass ()
return setmetatable ({....}, my_mt)
update:
There's an error in your updated code:
Router = {};
mt_for_router = {__index=Router}
--Router inherits from ElectronicDevice
Router = setmetatable({},{__index=ElectronicDevice});
Note that you initialize Router, and build mt_for_router from this; but then you reassign Router to a new table, while mt_for_router still points to the original Router.
Replace the Router={} with the Router = setmetatable({},{__index=ElectronicDevice}) (before the mt_for_router initialization).
Your updated code is wordy, but should work. Except, you have a typo that is breaking one of the metatables:
--Modem inherits from ElectronicDevice
Modem = setmetatable({},{__index,ElectronicDevice});
should read
--Modem inherits from ElectronicDevice
Modem = setmetatable({},{__index=ElectronicDevice});
The existing fragment made the Modem metatable be an array where the first element was almost certainly nil (the usual value of _G.__index unless you are using strict.lua or something similar) and the second element is ElectronicDevice.
The Lua Wiki description will make sense after you've grokked metatables a bit more. One thing that helps is to build a little infrastructure to make the usual patterns easier to get right.
I'd also recommend reading the chapter on OOP in PiL. You will want to re-read the chapters on tables and metatables too. Also, I've linked to the online copy of the 1st edition, but owning a copy of the 2nd is highly recommended. There is also a couple of articles in the Lua Gems book that relate. It, too, is recommended.
Another simple approach for subclass
local super = require("your base class")
local newclass = setmetatable( {}, {__index = super } )
local newclass_mt = { __index = newclass }
function newclass.new(...) -- constructor
local self = super.new(...)
return setmetatable( self, newclass_mt )
end
You still can use the functions from superclass even if overwritten
function newclass:dostuff(...)
super.dostuff(self,...)
-- more code here --
end
don't forget to use ONE dot when pass the self to the superclass function