In matlab, I have a class that ties up a shared resource during construction and releases it when deleted. (In my specific case, the shared resource is a port I can interact with it via http://localhost:1234)
It looks like this:
classdef myClass < handle
methods
function myClass()
OpenExecutableUsingPort1234();
end
function delete(this)
CloseExecutableUsingPort1234();
end
end
end
when I use it like this:
instance = myClass();
clear instance;
everything works fine. I open and close it without a problem. But when I use it like this:
instance = myClass();
instance = myClass(); % can't access the port during construction, because it's in use.
I can't start the executable because the port is in use.
The order of events is this:
a first copy of myClass is constructed. there is no problem with ports
the copy is assigned to the 'instance' variable.
a second copy of myClass is constructed. It can't start the executable because the port is in use
the new copy is assigned to the 'instance' variable
the first copy no longer has any references to it, and calls its delete method. This frees up the port.
What kind of workarounds are possible?
Ideally, I'd like step 5 to just know it needs to run early:
a first copy of myClass is constructed. there is no problem with ports
the copy is assigned to the 'instance' variable.
5. the first copy no longer has any references to it, and calls its delete method. This frees up the port.
a second copy of myClass is constructed. the port is free too!
the new copy is assigned to the 'instance' variable
You have to do a manual clear if you want the old instance to go away before the new instance starts creation.
If you do this:
instance = myClass();
instance = myClass();
The second line calls the myClass constructor before the instance variable gets reassigned, triggering GC on the first instance. It doesn't change instance until the second constructor returns, and it has a new value to put in it. (E.g. if the myClass constructor threw an error, instance should remain unchanged.)
Just stick a clear in there:
instance = myClass;
clear instance
instance = myClass;
(This behavior isn't guaranteed by the Matlab language specification as far as I can tell, but it's how things currently work.)
Example:
classdef Deletable < handle
properties
id
end
methods
function this = Deletable
persistent nextId
if isempty(nextId)
nextId = 1;
end
this.id = nextId;
nextId = nextId + 1;
fprintf('Hello! id=%d\n', this.id);
end
function delete(this)
fprintf('Bye bye! id=%d\n', this.id);
end
end
end
function without_clear
instance = Deletable;
instance = Deletable;
end
function with_clear
instance = Deletable;
clear instance
instance = Deletable;
end
Behavior:
>> without_clear
Hello! id=5
Hello! id=6
Bye bye! id=5
Bye bye! id=6
>>
>> with_clear
Hello! id=7
Bye bye! id=7
Hello! id=8
Bye bye! id=8
>>
If you really wanted it to work differently, you could write your class so that there can only be one active instance. Hold on to that in a persistent variable in the class definition. When a new instance is created, have it blow away the actual connection in the prior instance. I think that's kinda gross, though.
Or just work with a single Singleton instance of your class, and have its properties be mutable so you can alter its state instead of creating a new instance.
Related
I am trying to raise an event in Matlab. It is working, but not as i expected. What is going on is that the source object is passed as parameter do the event handler and i can't access my object properties inside the event handler.
Here is my class:
classdef MyClass < handle
properties
Name = 'a';
end
events
Changed
end
methods
function obj = MyClass(current)
if exist('current','var')
addlistener(current,'Changed',#ChangedHandle);
end
end
function obj = Change(obj,value)
notify(obj,'Changed');
end
function obj = ChangedHandle(obj,value)
disp(obj.Name);
end
end
end
These are the command lines to reproduce whats going on:
a = MyClass();
b = MyClass(a);
b.Name = 'b';
a.Change(3);
This is returning "a" and i want it to return "b".
Regards
The listener callback you specify receives details of the object raising the event. The fact that the listener has been created during the creation of a different object is not an intrinsic property of the listener – unless you effectively embed this in the design of your callback.
In your example, #ChangedHandle only works as a callback because it happens to be a method of the class current belongs to. If a and b belonged to different classes the problem would be more obvious: if ChangedHandle was a method only of a, it wouldn't know anything about the Name property of b. And if only a method of b, the listener bound to a would have only the function handle without any reference to the instance b of the class to which ChangeHandle belongs.
As described in the listener callback documentation you can use a method of the specific object instance current as an event listener using the syntax #current.ChangedHandle.
This method then receives callback arguments in the format callbackMethod(obj,src,evnt), so you need to add an argument to the definition of ChangedHandle. The first argument obj will be the instance referenced by current when the listener is created, so the line disp(obj.Name) will then produce the intended result without modification.
The reference to a received by the callback in the example will still be passed to the new callback – this is a fundamental behaviour of listener callbacks – but it will now be accessible from the second argument src.
In your addlistener code you are not telling the listener which object you want to call the method on, so what Matlab does it will apply the listener to the current object since thats all that was passed into the function, you can check this by changing the code to:
addlistener(current,'Changed',#(h,evt)current.ChangedHandle);
if you want it to run the code for the other class b (obj), then you use:
addlistener(current,'Changed',#(h,evt)obj.ChangedHandle);
I'm sorry I'm new to LUA scripts and I have to work on code written by others.
Please don't focus on code, my problem is only about included files and priority evaluating which function has to be called, in case of overriding.
Let's say I have a file Terrain.lua containing a class Terrain, which has a function Terrain:generate() and Terrain:generate() calls Terrain:getLatitude().
Terrain was included in a script MyScript.lua, which overrided Terrain:getLatitude() as follows:
include("Terrain");
function Terrain:getLatitude()
new code;
end
function myFunction()
local myTerrain = Terrain.create();
myTerrain.generate();
end
This has the effect of overriding getLatitude(): when myTerrain.generate() is called, generate() is the code from the included "Terrain", but getLatitude() is the local function with the new code, even if called by a function from the included class.
Now let's say I want to put some of the code in an external file Custom.lua. Custom (and not MyScript) has to override getLatitude().
This is the situation:
Terrain.lua contains Terrain class and these functions
Terrain.create()
Terrain.generate()
Terrain.getLatitude()
MyScript.lua is the script being executed, and include Custom:
include("Custom");
function myFunction()
return customFunction()
end
Custom.lua contains:
include("Terrain");
function Terrain:getLatitude()
new code;
end
function customFunction()
local myTerrain = Terrain.create();
myTerrain.generate();
end
Now, if I call customFunction() from MyScript, getLatitude() from Terrain is used, instead of getLatitude() from Custom. I assume ovveride is possible only inside the currenti file being executed? How can I achieve overriding in an included file?
I hope this example is enough to understand my problem, without posting a lot of code. Thank you.
Firstly, some corrections: there is no local function's in your question; include is not part of any lua standard, what that function actually does may be quite important.
Finally, Lua does not have actual class system, what you use in the question is merely a syntactic sugar (misleading and confusing as I find it) over table assignments. Lua is an interpreted language, so what may seem to you as a class definition is not a static structure known from the very beginning of the program execution but a code that gets executed from the top of the file to the bottom.
Thus, if we assume that include is similar to the require, then the your question code will be equivalent to the following:
do--terrain.lua
Terrain = {
create=function()
local created_object
--some code to assign value to created_object
return created_object
end
}
Terrain.generate = function(self) end
Terrain.getLatitude = function(this_is_a_self_too)
--some code that uses `also_self` as a reference to the object when called as object:generate()
end
--do end block is essentially an equivalent of file, its local variables are not seen outside
--global variables it has assigned (like `terrain`) will stay accessible AFTER its end
--changes it done to global variables will also remain
end
do--Custom.lua
Terrain.getLatitude = function(this)--this is the assignment to a field in a table stored in the global variable Terrain
--this function will replace the one assigned to the `getLatitude` field
end
customFunction = function()
local myTerrain = Terrain.create();
myTerrain.generate();--this one probably needs `:` instead of `.`
--depends on actual code inside terrain.lua
end
end
do--MyScript.lua
myFunction= function()
return customFunction() --this line calls the global variable customFunction
end
end
Thus if your actual setup is similar to the one in question, then the "override" will take effect after the Custom.lua is executed and for all the subsequent calls to the Terrain.getLatitude regardless of whether or not they've called the file. (And any later file can override it again, and all calls after that will be using the new one)
It is probably more complicated to do a limited override in this setup. That again depends on the actual details of how your team has defined the Terrain class and the class system itself.
I'm trying to wrap my head around classes in AHK. I'm C++ dev, so would like to make use of RAII (__New, __Delete), but it looks like I miss some concepts since things look very contra-intuitive for me.
After some tries I came up with this simple example:
class Scenario
{
__New()
{
MsgBox, NEW
}
__Delete()
{
MsgBox, DELETE
}
}
scenario := new Scenario
scenario := new Scenario
scenario := 1
scenario := {}
scenario := new Scenario
Return
As a result I get the following messages:
NEW
NEW
DELETE
DELETE
Questions:
Why doesn't the object get destroyed during the second assignment? I'd assume the number of refs going to 0, no?
How come I get 2 destructions in a row? Where was that object stored meanwhile? How could scenario variable hold both references?
Why was not the third construction called?
Why doesn't the object get destroyed during the second assignment?
Garbage collection had not been triggered yet
I'd assume the number of refs going to 0, no?
References going to 0 does not necessarily trigger GC
How come I get 2 destructions in a row?
Garbage collection cleaned both references at the same time
Where was that object stored meanwhile?
The heap
How could scenario variable hold both references?
scenario does not hold both references
Why was not the third construction called?
Only two Scenario objects are constructed. The variable scenario is a dynamic variable and is not always an instance of the class Scenario. The last assignment scenario := {} just creates an empty object.
Ok, found out what was missing. Two things:
AHK script is case-insensitive.
Since class is an object by itself in AHK it's possible to override the class by another object.
Here is a piece of the documentation:
Because the class is referenced via a variable, the class name cannot be used to both reference the class and create a separate variable (such as to hold an instance of the class) in the same context. For example, box := new Box would replace the class object in Box with an instance of itself. [v1.1.27+]: #Warn ClassOverwrite enables a warning to be shown at load time for each attempt to overwrite a class.
This explains what happened in the code above: variable name scenario is effectively the same as a class name Scenario, so I just quietly overrode my class with an empty object.
Also, since the new instance of the class is created before assignment, I got two 'NEW' in a row, only than 'DELETE'.
Suppose I have two classes, a < matlab.mixin.Copyable and b < handle.
Here's a:
classdef a < matlab.mixin.Copyable
properties
hListener
end
methods
function obj = a(b)
obj.newListener(b);
end
function newListener(obj, b)
obj.hListener = addlistener(b, 'bEvent', #(o,e) disp('Received bEvent'));
end
function sobj = saveobj(obj)
sobj = copy(obj);
delete(sobj.hListener);
sobj.hListener = [];
end
end
end
and b:
classdef b < handle
events
bEvent
end
methods
function obj = b
end
function raiseEvent(obj)
notify(obj, 'bEvent');
end
end
end
I use saveobj in a to delete the listener upon save. I will manually re-instantiate a listener when I load the classes later.
a inherits from matlab.mixin.Copyable so I can do a copy during the saveobj operation - this way I can make a copy of the original object and change it, without affecting the original, and then save that to a MAT-file. (Supposedly - this is where my question is going.)
Now I run the following at the command line:
>> bob = b; alice = a(bob);
>> bob.raiseEvent
Received bEvent
Everything works. Now, let's save:
>> save ab alice bob
and try to raise the event again:
>> bob.raiseEvent
% NOTHING HAPPENS!
Turns out the listener is gone!
>> alice.hListener
handle to deleted listener
What is going on here? Why is the listener object shared between sobj and obj in the saveobj method?
In researching this question I discovered the answer. I figured I'd add to the body of knowledge around these parts.
Per the documentation for matlab.mixin.Copyable,
Given a call to the matlab.mixin.Copyable copy method of the form:
B = copy(A);
Under the following conditions, produces the described results:
A has dynamic properties — copy does not copy dynamic properties. You can implement dynamic-property copying in the subclass if needed.
A has no non-Dependent properties — copy creates a new object with no property values without calling the class constructor to avoid introducing side effects.
A contains deleted handles — copy creates deleted handles of the same class in the output array.
A has attached listeners — copy does not copy listeners. (emphasis added)
A contains objects of enumeration classes — Enumeration classes cannot subclass matlab.mixin.Copyable.
A delete method calls copy — copy creates a legitimate copy, obeying all the behaviors that apply in any other usage.
What I took this to mean originally was that a copy call would skip over a property whose value is a listener handle. Apparently what it means is that rather than skipping the property altogether, copy makes a reference to the original listener object, i.e., it copies the handle to the listener but not the listener itself.
Of course, when you load the copied object and it has that reference to listener, it'll complain:
Warning: Cannot load an object of class 'listener':
No matching constructor signature found.
Warning: During load:
An invalid default object has been detected while loading a heterogeneous array of
class event.listener. An empty array of class event.listener will be returned.
The easiest thing to do, then, is to modify saveobj:
function sobj = saveobj(obj)
sobj = copy(obj);
sobj.hListener = [];
end
Here, I don't explicitly call delete on the listener, as this would delete the actual object. Instead, I just clear the reference to that object.
Context
I created two similar PrimarySettings and SecondarySettings handle classes in matlab to store settings of my own and was willing to group both of them in a bigger AllSettings class for convenience.
All classes have a PropertyChanged event to monitor for settings modifications.
The code for all classes can be visualized from here.
Problem
When I test PrimarySettings and SecondarySettings classes to check if their are effectively calling their delete method when all instances are no longer referenced in the code I have no issue:
>> ps = PrimarySettings();
>> clear ps;
Destroying PrimarySettings.
>> ss = secondarySettings();
>> clear ss;
Destroying SecondarySettings.
The same when I test for proper event notifications:
>> as = AllSettings();
>> as.PrimarySettings.Titi = 12;
Property 'PrimarySettings.Titi' modified.
The problem is only with the AllSettings class for which delete method is never called at all:
>> as = AllSettings();
>> clear as;
!!!!!! here delete method is not called !!!!!
The object is still in memory while it is no longer referenced. This can be tested like this:
>> clear classes
Warning: Objects of 'SecondarySettings' class exist. Cannot clear this class or any of its superclasses.
Warning: Objects of 'PrimarySettings' class exist. Cannot clear this class or any of its superclasses.
Warning: Objects of 'AllSettings' class exist. Cannot clear this class or any of its superclasses.
Investigations
The problem seems linked to the way I am attaching the PropertyChanged event (the listener is probably still alive).
Unfortunately I truly don't understand why. Indeed, the code for attaching to events is very similar in all the classes I created.
PrimarySettings and SecondarySettings:
for idx = 1:propCount;
listener = addlistener(obj, meta.PropertyList(idx).Name, 'PostSet', #obj.onPropertyChanged);
listener.Recursive = true;
end
AllSettings:
for idx = 1:propCount;
propName = meta.PropertyList(idx).Name;
listener = addlistener(obj, propName, 'PostSet', #obj.onPropertyChanged);
listener.Recursive = true;
listener = addlistener(obj.(propName), 'PropertyChanged', #obj.onSubPropertyChanged);
listener.Recursive = true;
end
NB: If I comment the second listener (i.e. onSubPropertyChanged) then the AllSettings class works as expected!
Question
What's wrong with the deletion of AllSettings objects ?
If you need a specific diagnosis of the issue, I'm afraid you'll need to post more code, so we can trace through what's going on exactly.
But I would expect that the issue is that one of your listeners is storing a reference to another of your objects in a way that you're not expecting, so it's not getting destroyed when you call clear.
To help you diagnose things further, note that there are two ways to create listeners:
Using addlistener, which ties the listener's lifecycle to the object that is the source of the event.
Constructing the listener directly using event.listener, where the listener exists only while it is in scope, and is not tied to the event-generating object's existence.
For more information, see the section "Ways to create listeners" in this documentation page on Events and Listeners — Syntax and Techniques.
Hope that helps!