Code covered by the BSD License  

Highlights from
MagicSynchronizer

from MagicSynchronizer by Benoit Charles
MagicSynchronizer creates an object to synchronize a set of properties.

MagicSynchronizer
% MAGICSYNCHRONIZER Creates an object to synchronize a set of properties.
% 
% ms = MagicSynchronizer( callback, ...
%		[ 'EXE' ], ...
%		[ 'IN', obj, prop, [ prop2, [...] ], [ obj2, ... ] ], ...
%		[ 'OUT', obj, prop, [ prop2, [...] ], [ obj2, ... ] ], ...
%		[ 'DEP', obj, [ obj2 ... ] ], ...
%		[ 'CHECKIN', check ], ...
%		[ 'NAME', name ] );
%	creates a new synchronizer
%
%	The callback is a function handle, which doesn't take any argument and
%	doesn't return any value. This callback will be called when any 'input'
%	properties has been changed ('IN' set).
%	
%	'DEP' is an optionnal set, and can be use to link the life cycle of
%	the synchronizer with another objects.
%
%	'EXE' is an optional flag. When it's given, the callback is called once
%	in the constructor.
%
%	'CHECKIN' is an optional boolean (true per default). When it's false,
%	the synchronizer don't check if the input has been changed. You can
%	change directly this value when the synchronizer is created whith the
%	property 'CheckIn'.
%
%	'NAME' is an optionnal argument, only useful for debugging.
%
%	If the string 'copy' is given instead the callback, a default callback
%	which copy "out" properties to "in" emplacements is used.
%
%	When a synchronizer is created, it searchs automatically from what
%	synchronizer it depends, and what synchronizers are depending from it.
%	So, when a synchronizer is launch, it launchs its children too.
%
%	You can't launch a specific synchronizer, but you can notify you have
%	changed some property values, and the synchronizers which are listening
%	theses properties will be lauch. To notify some changements, you have
%	to use the class function MagicSynchronizer.synchronize:
%		MagicSynchronizer.synchronize( [obj] | [obj,'prop',['prop2',...]], ... );
%
%	Synchronizers save the last values of properties: a synchronizer calls
%	the callback only if at least one input property is modified.
%
%	The callback can interrupt the progress (e.g. the user has clicked on
%	the famous "cancel" button), with throwing a 
%	MagicSynchronizer.interruptionError error. The goal of synchronizer is
%	to keep a homogeous state beetwen all given properties. So, when this
%	error appends, the inputs properties are restored to the last "stable"
%	version, also the properties of the synchronizer's parents. If any else
%	error appends, the error is immediatly rethrown: the properties are not
%	synchronized.
%
%	Sometimes, during the developpment of a programm, you need to delete
%	every synchronizers. To do that:
%	MagicSynchronizer.nukeall();
%	Of course, absolutly all synchronizer will be destructed.
%	This command can help a "clear classes".
%	

classdef MagicSynchronizer < handle
	
	%% ### INSTANCE PROPERTIES AND METHODS
	
	%% Properties
	
	properties
		CheckIn
	end
	
	properties( Access = private )
		
		inid
		outid
		
		parents
		children
				
		hasToBeExecutedFlag = 0;
		callback
		
		lifeCycleListenerObj
		
		executionIndex = 0;
		
	end
	
	properties
		Name = '';
	end
	
	%% Constructor
	
	methods
		
		function this = MagicSynchronizer( callback, varargin )
			
			persistent cycleError argumentError
			if isempty( cycleError )
				cycleError = MagicSynchronizer.cycleError;
				cycleError.message = 'You''re killing me? You''re creating a cyclic synchronizer!';
				
				argumentError = struct( ...
					'identifier', 'MagicSynchronizer:UnexpectedArguments', ...
					'message', 'Unexpected arguments' );
			end
			
			if nargin == 0
				return
			end
			
			% read arguments
			currentState = '';
			in  = struct( 'object', {}, 'property', {} );
			out  = struct( 'object', {}, 'property', {} );
			depends = {};
			name = '';
			checkin = true;
			exeflag = false;
			
			i = 1; nbargs = length( varargin );
			while i <= nbargs
				
				arg = varargin{i};
				if ischar( arg ) && any( strcmp( arg, { 'IN', 'OUT', 'DEP', 'NAME', 'CHECKIN' } ) )
					currentState = arg;
				elseif ischar( arg ) && strcmp( arg, 'EXE' )
					exeflag = true;
					
					
				elseif strcmp( currentState, 'IN' ) && isa( varargin{i}, 'handle' ) && ischar( varargin{i+1} )
					in( end+1 ) = struct( 'object', varargin{i}, 'property', varargin{i+1} ); %#ok<AGROW>
					i = i+1;
				elseif strcmp( currentState, 'IN' ) && ischar( varargin{i} )
					in( end+1 ) = struct( 'object', in(end).object, 'property', varargin{i} ); %#ok<AGROW>
					
					
				elseif strcmp( currentState, 'OUT' ) && isa( varargin{i}, 'handle' ) && ischar( varargin{i+1} )
					out( end+1 ) = struct( 'object', varargin{i}, 'property', varargin{i+1} ); %#ok<AGROW>
					i = i+1;
				elseif strcmp( currentState, 'OUT' ) && ischar( varargin{i} )
					out( end+1 ) = struct( 'object', out(end).object, 'property', varargin{i} ); %#ok<AGROW>
					
					
				elseif strcmp( currentState, 'DEP' ) && isa( varargin{i}, 'handle' )
					depends{ end+1 } = varargin{i}; %#ok<AGROW>
					
					
				elseif strcmp( currentState, 'CHECKIN' ) && isa( varargin{i}, 'logical' )
					checkin = varargin{i};
					
					
				elseif strcmp( currentState, 'NAME' ) && ischar( varargin{i} )
					name = varargin{i};
					
					
				else
					error( argumentError );
				end
				
				i = i+1;
				
			end
			
			nbin = length( in );
			nbout = length( out );
			
			% record the checkin flag
			this.CheckIn = checkin;
			
			% record name
			this.Name = name;
			
			% record callback
			
			function copyCallback( nb, in, out )
				for iii = 1:nb
					out(iii).object.( out(iii).property ) = in(iii).object.( in(iii).property );
				end
			end
			
			if ischar( callback ) && strcmp( callback, 'copy' ) && nbin == nbout
				callback = @() copyCallback( nbin, in, out );
			end
			
			this.callback = callback;
			
			% run once the callback
			if exeflag
				callback();
			end
			
			% get all depending object
			function addDepends( obj )
				if ~any( cellfun( @(o) o==obj, depends ) )
					depends = [ depends {obj } ];
				end
			end
			for i = 1:nbin, addDepends( in(i).object ); end
			for i = 1:nbout, addDepends( out(i).object ); end
			
			% create life cycle listener
			this.lifeCycleListenerObj = event.listener( depends, 'ObjectBeingDestroyed', @(H,E) destroy() );
			function destroy()
				if isvalid( this )
					delete( this );
				end
			end
			
			% record in and out properties
			idin = zeros( 1, nbin );
			for i = 1:nbin
				idin( i ) = MagicSynchronizer.propertiesManager( 'add', 'in', in(i).object, in(i).property );
			end
			this.inid = idin;
			
			idout = zeros( 1, nbout );
			for i = 1:nbout
				idout( i ) = MagicSynchronizer.propertiesManager( 'add', 'out', out(i).object, out(i).property );
			end
			this.outid = idout;
			
			if ~isempty( intersect( idin, idout ) )
				error( cycleError );
			end
			
			% record and get parents and children
			[ this.parents, this.children ] = MagicSynchronizer.synchronizersManager( 'add', this );
			if ~isempty( this.parents ), this.parents.addChild( this ); end
			if ~isempty( this.children ), this.children.addParent( this ); end
			
		end
		
	end
	
	%% Destructor
	
	methods
		
		function delete( this )
			MagicSynchronizer.synchronizersManager( 'rem', this );
			if ~isempty( this.parents )
				this.parents.remChild( this );
				this.parents = [];
			end
			if ~isempty( this.children )
				this.children.remParent( this );
				this.children = [];
			end
		end
		
	end
	
	%% Display
	
	methods
		
		function str = tostring( this )
			
			function final = join( elts, j )
				if nargin == 1, j = ' '; end
				if isempty( elts )
					final = '';
				elseif length( elts ) == 1
					final = elts{1};
				else
					final = [ elts{1} sprintf( [ j, '%s' ], elts{2:end} ) ];
				end
			end
			function str = idprop2string( idprop )
				str = '';
				[ obj, prop ] = MagicSynchronizer.propertiesManager( 'get', idprop );
				if ~isempty( prop )
					meta = metaclass( obj );
					str = sprintf( '%s.%s', meta.Name, prop );
				end
			end
			
			strexe = '';
			if this.hasToBeExecutedFlag ~= 0
				strexe = '(*) ';
			end
			
			strname = '';
			if ~isempty( this.Name )
				strname = sprintf( '%s : ', this.Name );
			end
			
			strin = join( arrayfun( @(id) idprop2string(id), this.inid, 'UniformOutput', false ), ', ' );
			strout = join( arrayfun( @(id) idprop2string(id), this.outid, 'UniformOutput', false ), ', ' );
			str = sprintf( '%s%s%s > %s > %s', strexe, strname, strin, func2str( this.callback ), strout );
			
		end
		
		function display( array )
			
			function final = join( elts, j )
				if nargin == 1, j = ' '; end
				if isempty( elts )
					final = '';
				elseif length( elts ) == 1
					final = elts{1};
				else
					final = [ elts{1} sprintf( [ j, '%s' ], elts{2:end} ) ];
				end
			end
			function c = arraytostring( syncs )
				l = length( syncs );
				c = cell( 1, l );
				for j = 1:l
					c{j} = syncs(j).tostring();
				end
			end
			
			larray = length( array );
			
			fprintf( '\n\tNB SYNCHRONIZERS : %d\n\n', larray );
			
			sep = repmat( '-', 1, 30 );
			for i = 1:larray
				
				sync = array(i);
				
				fprintf( '%d %s\n%s\n\nPARENTS:\n%s\n\nCHILDREN:\n%s\n\n', ...
					i, sep, ...
					sync.tostring(), ...
					join( arraytostring( sync.parents ), '\n' ), ...
					join( arraytostring( sync.children ), '\n' ) );
				
			end
			
		end
		
	end
	
	%% Family matters
	
	methods( Access = private )
		
		function addParent( syncs, sync )
			for i = 1:length( syncs )
				syncs(i).parents( end+1 ) = sync;
			end
		end
		
		function remParent( syncs, sync )
			for i = 1:length( syncs )
				if isvalid( syncs(i) )
					syncs(i).parents( syncs(i).parents==sync ) = [];
				end
			end
		end
		
		function addChild( syncs, sync )
			for i = 1:length( syncs )
				syncs(i).children( end+1 ) = sync;
			end
		end
		
		function remChild( syncs, sync )
			for i = 1:length( syncs )
				if isvalid( syncs(i) )
					syncs(i).children( syncs(i).children==sync ) = [];
				end
			end
		end
		
		function path = searchALoop( this, pathFrom )
			
			path = [];
			
			for i = 1:length( pathFrom )
				if pathFrom(i) == this
					path = [ pathFrom(i:end) this ];
					return
				end
			end
			
			pathFrom( end+1 ) = this;
			
			if ~isempty( this.parents )
				for i = 1:length( this.parents )
					path = this.parents(i).searchALoop( pathFrom );
					if ~isempty( path )
						return
					end
				end
			end
			
		end
		
	end
	
	%% Execution
	
	methods( Access = private )
		
		function setToBeExecuted( syncs, flagValue, registerCallback )
			
			for i = 1:length( syncs )
				if syncs(i).hasToBeExecutedFlag == 0
					
					syncs(i).hasToBeExecutedFlag = flagValue;
					syncs(i).executionIndex = registerCallback( syncs(i) );
					
					if ~isempty( syncs(i).children )
						syncs(i).children.setToBeExecuted( flagValue, registerCallback );
					end
					
				elseif syncs(i).hasToBeExecutedFlag ~= flagValue
					
					error( MagicSynchronizer.doubleExecutionError.identifier, ...
						'You try to execute this synchonizer twice:\n%s', syncs(i).tostring() ); 
					
				end
			end
			
		end
		
		function setToBeNotExecuted( syncs )
			
			for i = 1:length( syncs )
				syncs(i).hasToBeExecutedFlag = 0;
				if ~isempty( syncs(i).children )
					syncs(i).children.setToBeNotExecuted();
				end
			end
			
		end
		
		function execute( this, checkExecutionCallback )
			
			persistent interruptionErrorId
			if isempty( interruptionErrorId )
				interruptionErrorId = MagicSynchronizer.interruptionError.identifier;
			end
			
			% don't run if not need
			if ~isvalid( this ) || ( this.hasToBeExecutedFlag == 0 )
				return
			end
			
			% test if any parent needs to be executed before
			if ~isempty( this.parents ) && any( [ this.parents.hasToBeExecutedFlag ]~=0 )
                return
			end
			
			checkExecutionCallback( this.executionIndex );
			ch = this.children;
			
			% test if the callback really needs to be executed
			exeFlag = ~this.CheckIn || any( MagicSynchronizer.propertiesManager( 'test', this.inid ) );
			
			% execute
			if exeFlag
				try
					this.callback();
				catch err
					MagicSynchronizer.propertiesManager( 'check', this.outid );
					rethrow( err );
				end
				if isvalid( this ), MagicSynchronizer.propertiesManager( 'check', this.outid ); end
			end
			if isvalid( this ), this.hasToBeExecutedFlag = 0; end
			
			% execute children
			ch = ch( isvalid( ch ) );
			for i = 1:length( ch )
				ch(i).execute( checkExecutionCallback );
			end
			
		end
		
	end
	
	%% ### CLASS PROPERTIES AND METHODS
	
	%% Interruption error
	
	properties( Constant )
		
		interruptionError = struct( 'identifier', 'MagicSynchronizer:Interrupted' );
		cycleError = struct( 'identifier', 'MagicSynchronizer:CycleFound' );
		doubleExecutionError = struct( 'identifier', 'MagicSynchronizer:DoubleExecutionError' );
		
	end
	
	%% Launch synchronizers
	
	methods( Static )
		
		function varargout = exeflagManager( order, varargin )
			
			persistent usedFlag
			
			switch order
				
				case 'get'
					flag = find( [ ~usedFlag true ], 1 );
					usedFlag( flag ) = true;
					varargout = { flag };
					
				case 'release'
					usedFlag( varargin{1} ) = false;
					
			end
			
		end
		
		function synchronize( varargin )
			persistent cycleErrorId interruptionErrorId
			if isempty( cycleErrorId )
				cycleErrorId = MagicSynchronizer.cycleError.identifier;
				interruptionErrorId = MagicSynchronizer.interruptionError.identifier;
			end
			
			waitbarflag = false;
			if ischar( varargin{1} ) && strcmp( varargin{1}, 'WAITBAR' )
				waitbarflag = true;
				varargin = varargin(2:end);
			end
			
			syncprops = struct( ...
				'obj', {}, ...
				'prop', {}, ...
				'ids', {} );
			prevIsObj = false;
			
			for i = 1:length( varargin )
				if ischar( varargin{i} )
					if ~prevIsObj
						syncprops( end+1 ).obj = syncprops( end ).obj; %#ok<AGROW>
					end
					syncprops( end ).prop = varargin{i};
					prevIsObj = false;
				else
					syncprops( end+1 ).obj = varargin{i}; %#ok<AGROW>
					prevIsObj = true;
				end
			end
			
			% get id of properties
			for i = 1:length( syncprops )
				if isempty( syncprops(i).prop )
					syncprops(i).ids = MagicSynchronizer.propertiesManager( 'object', syncprops(i).obj );
				else
					syncprops(i).ids = MagicSynchronizer.propertiesManager( 'property', syncprops(i).obj, syncprops(i).prop );
				end
			end
			ids = unique( [ syncprops.ids ] );
			if isempty( ids )
				return
			end
			
			% check properties
			MagicSynchronizer.propertiesManager( 'check', ids )
			
			% get synchronizers
			syncs = MagicSynchronizer.synchronizersManager( 'depends', ids );
			
			% initialize synchronizers
			
			exeflag = MagicSynchronizer.exeflagManager( 'get' );
			
			bufferSize = 20;
			buffer(1, 1:bufferSize) = MagicSynchronizer();
			listSync = buffer;
			syncsSize = bufferSize;
			nbSyncs = 0;
			function ind = registerFct( sync )
				if nbSyncs+1 > syncsSize
					listSync = [ listSync buffer ];
					syncsSize = syncsSize + bufferSize;
				end
				nbSyncs = nbSyncs + 1;
				listSync( nbSyncs ) = sync;
				ind = nbSyncs;
			end
			
			try
				syncs.setToBeExecuted( exeflag, @registerFct );
			catch err
				listSync = listSync( 1:nbSyncs );
				listSync = listSync( isvalid( listSync ) );
				listSync.setToBeNotExecuted();
				MagicSynchronizer.exeflagManager( 'release', exeflag );
				rethrow( err );
			end
			
			listSync = listSync( 1:nbSyncs );
			
			% run synchronizers
			
			checkList = false( 1, nbSyncs );
			function checkExecutionFct( ind )
				checkList( ind ) = true;
			end
			
			if waitbarflag
				hw = waitbar( 0, 'Please wait...' );
			end
			
			try
				for i = 1:length( syncs )
					syncs(i).execute( @checkExecutionFct );
					if waitbarflag
						waitbar( mean( checkList ), hw );
					end
				end
			catch err
				if strcmp( err.identifier, interruptionErrorId )
					runnedSync = listSync( checkList ); runnedSync = runnedSync( isvalid( runnedSync ) );
					ids = unique( [ runnedSync.inid runnedSync.outid ] );
					ids = ids( MagicSynchronizer.propertiesManager( 'test', ids ) );
					MagicSynchronizer.propertiesManager( 'restore', ids )
					return
				else
					listSync = listSync( isvalid( listSync ) );
					listSync.setToBeNotExecuted();
					MagicSynchronizer.exeflagManager( 'release', exeflag );
					getReport( err )
					rethrow( err );
				end
			end
			
			if waitbarflag
				delete( hw )
			end
			
			% get new properties values
			runnedSync = listSync( checkList ); runnedSync = runnedSync( isvalid( runnedSync ) );
			ids = unique( [ runnedSync.inid runnedSync.outid ] );
			MagicSynchronizer.propertiesManager( 'save', ids )
			
			% release the flag
			MagicSynchronizer.exeflagManager( 'release', exeflag );
			
			% check loops
			if ~all( checkList )
				
				% search a loop
				path = listSync( find( ~checkList, 1 ) ).searchALoop( MagicSynchronizer.empty(1,0) );
				
				% create a string from linked properties
				nbl = length( path );
				linkInProp = { path.inid };
				linkOutProp = { path.outid };
				linkProp = cell( 1, nbl );
				for i = 1:nbl
					strin = join( arrayfun( @(id) idprop2string(id), linkInProp{i}, 'UniformOutput', false ), ', ' );
					strout = join( arrayfun( @(id) idprop2string(id), linkOutProp{i}, 'UniformOutput', false ), ', ' );
					linkProp{nbl-i+1} = sprintf( '%s -> %s', strin, strout );
				end
				links = join( linkProp, '\n' );
				
				% create error
				error( cycleErrorId, 'A loop has been found!\n%s', links );
				
			end
			
			function final = join( elts, j )
				if nargin == 1, j = ' '; end
				if isempty( elts )
					final = '';
				elseif length( elts ) == 1
					final = elts{1};
				else
					final = [ elts{1} sprintf( [ j, '%s' ], elts{2:end} ) ];
				end
			end
			function str = idprop2string( idprop )
				str = '';
				[ obj, prop ] = MagicSynchronizer.propertiesManager( 'get', idprop );
				if ~isempty( prop )
					meta = metaclass( obj );
					str = sprintf( '%s.%s', meta.Name, prop );
				end
			end
			
		end
		
	end
	
	%% Nuke 'em all!!
	
	methods( Static )
		
		function nukeall()
			MagicSynchronizer.synchronizersManager( 'remall' );
			MagicSynchronizer.propertiesManager( 'remall' );
		end
		
	end
	
	%% Properties manager
	
	methods( Static, Access = private )
		
		% id = MagicSynchronizer.propertiesManager( 'add', ['in'|'out'], obj, prop )
		% ids = MagicSynchronizer.propertiesManager( 'object', obj )
		% id = MagicSynchronizer.propertiesManager( 'property', obj, prop )
		% [ obj, prop ] = MagicSynchronizer.propertiesManager( 'get', id )
		% MagicSynchronizer.propertiesManager( 'save', [ ids ] )
		% MagicSynchronizer.propertiesManager( 'restore', [ ids ] )
		% MagicSynchronizer.propertiesManager( 'check', [ ids ] )
		% [ isModified ] = MagicSynchronizer.propertiesManager( 'test', [ idsToCheck ] )
		% MagicSynchronizer.propertiesManager( 'remall' )
		function varargout = propertiesManager( order, varargin )
			
			persistent propBufferSize propBuffer properties propSize nbProp
			persistent objBufferSize objBuffer objects objSize nbObjects
			persistent lastid
			if isempty( lastid )
				init()
			end
			
			function init()
				
				propBufferSize = 100;
				propBuffer = repmat( struct( ...
					'id', [], ...
					'object', [], ...
					'name', [], ...
					'state', [], ...
					'verified', [], ...
					'modified', [] ), 1, propBufferSize );
				properties = propBuffer;
				propSize = propBufferSize;
				nbProp = 0;
				
				objBufferSize = 10;
				objBuffer = repmat( struct( ...
					'object', [], ...
					'inids', [], ...
					'outids', [], ...
					'lifeCycleListener', [] ), 1, objBufferSize );
				objects = objBuffer;
				objSize = objBufferSize;
				nbObjects = 0;
				
				lastid = 0;
				
			end
			
			switch order
				
				case 'remall'
					% reinitialize
					init();
					varargout = {};
					
				case 'add'
					% add a new property
					[ flag, obj, prop ] = deal( varargin{:} );
					
					% search indexes of an existing property
					[ indObj, indProp ] = searchIndex( obj, prop );
					
					% create a new object
					if isempty( indObj )
						
						if nbObjects+1 > objSize
							objects = [ objects objBuffer ];
							objSize = objSize + objBufferSize;
						end
						
						nbObjects = nbObjects+1;
						objects( nbObjects ).object = obj;
						objects( nbObjects ).lifeCycleListener = event.listener( obj, 'ObjectBeingDestroyed', @(H,E) deleteObject( obj ) );
						
						indObj = nbObjects;
						
					end
					
					% create a new property
					if isempty( indProp )
						
						lastid = lastid + 1;
						propid = lastid;
						
						if nbProp+1 > propSize
							properties = [ properties propBuffer ];
							propSize = propSize + propBufferSize;
						end
						
						nbProp = nbProp + 1;
						properties( nbProp ).id = propid;
						properties( nbProp ).object = obj;
						properties( nbProp ).name = prop;
						properties( nbProp ).state = obj.( prop );
						properties( nbProp ).modified = false;
						
					else
						
						propid = properties( indProp ).id;
						
					end
					
					% record id in the object struct
					if strcmp( flag, 'in' )
						objects( indObj ).inids = unique( [ objects( indObj ).inids propid ] );
					else
						objects( indObj ).outids = unique( [ objects( indObj ).outids propid ] );
					end
					
					varargout = { propid };
					
				case 'save'
					% save new values of properties
					ids = varargin{1};
					
					[ isrec, loc ] = ismember( ids, [ properties(1:nbProp).id ] );
					
					for k = loc( isrec )
						newState = properties(k).object.( properties( k ).name );
						properties(k).state = newState;
						properties(k).modified = false;
					end
					
				case 'restore'
					% restore saved properties values
					ids = varargin{1};
					
					[ isrec, loc ] = ismember( ids, [ properties(1:nbProp).id ] );
					
					for k = loc( isrec )
						properties(k).object.( properties( k ).name ) = properties(k).state;
						properties(k).modified = false;
					end
					
				case 'check'
					% check if properties has been modified
					ids = varargin{1};
					
					[ isrec, loc ] = ismember( ids, [ properties(1:nbProp).id ] );
					
					for k = loc( isrec )
						newState = properties( k ).object.( properties( k ).name );
						properties( k ).modified = ~isequal( newState, properties( k ).state );
					end
					
				case 'test'
					% return if properties has been modified
					ids = varargin{1};
					
					[ isrec, loc ] = ismember( ids, [ properties(1:nbProp).id ] );
					ret = false( 1, length( ids ) );
					ret( isrec ) = [ properties( loc(isrec) ).modified ];
					varargout = { ret };
					
				case 'object'
					% return all 'in' id of an object
					obj = varargin{1};
					
					indObj = find( cellfun( @(o) o==obj, { objects(1:nbObjects).object } ), 1 );
					if ~isempty( indObj )
						varargout = { objects( indObj ).inids };
					else
						varargout = { [] };
					end
					
				case 'property'
					% return id of a specific property
					[ obj, prop ] = deal( varargin{:} );
					
					[ indObj, indProp ] = searchIndex( obj, prop );
					if ~isempty( indProp )
						varargout = { properties( indProp ).id };
					else
						varargout = { [] };
					end
					
				case 'get'
					% return object and property from an id
					id = varargin{1};
					
					[ isrec, loc ] = ismember( id, [ properties(1:nbProp).id ] );
					if isrec
						varargout = { properties(loc).object, properties(loc).name };
					else
						varargout = { [], '' };
					end
					
			end
			
			% search indexes from object and name property
			function [ indObj, indProp ] = searchIndex( obj, prop )
				
				% search the object
				indObj = find( cellfun( @(o) o==obj, { objects(1:nbObjects).object } ), 1 );
				if isempty( indObj )
					indProp = [];
					return
				end
				
				% search the property
				idsProp = unique( [ objects( indObj ).inids objects( indObj ).outids ] );
				inds = find( ismember( [ properties( 1:nbProp ).id ], idsProp ) );
				indProp = inds( find( strcmp( prop, { properties( inds ).name } ), 1 ) );
				
			end
			
			% object has been deleted
			function deleteObject( obj )
				
				% search object
				indObj = find( cellfun( @(o) o==obj, { objects(1:nbObjects).object } ), 1 );
				if isempty( indObj )
					return
				end
				
				% delete properties
				ids = unique( [ objects( indObj ).inids objects( indObj ).outids ] );
				[ ind, loc ] = ismember( ids, [ properties(1:nbProp).id ] );
				properties( loc( ind ) ) = [];
				propSize = propSize - nnz(ind);
				nbProp = nbProp - nnz(ind);
				
				% delete object
				objects( indObj ) = [];
				objSize = objSize - 1;
				nbObjects = nbObjects - 1;
				
			end
			
		end
		
	end
	
	%% Synchronizers manager
	
	methods( Static, Access = private )
		
		% [ parent, children ] = MagicSynchronizer.synchronizersManager( 'add', sync )
		% MagicSynchronizer.synchronizersManager( 'rem', sync )
		% [ children ] = MagicSynchronizer.synchronizersManager( 'depends', idprops )
		% MagicSynchronizer.synchronizersManager( 'remall' )
		function varargout = synchronizersManager( order, varargin )
			
			persistent recursiveFlag bufferSize buffer syncs syncsSize nbSyncs
			if isempty( recursiveFlag )
				recursiveFlag = false;
				initialize()
			end
			
			function initialize()
				bufferSize = 100;
				temp(1, 1:bufferSize) = MagicSynchronizer();
				buffer = temp;
				
				syncs = buffer;
				syncsSize = bufferSize;
				nbSyncs = 0;
			end
			
			if recursiveFlag
				return
			else
				recursiveFlag = true; %#ok<NASGU>
			end
			
			switch order
				
				case 'add'
					
					sync = varargin{1};
					
					if nbSyncs+1 > syncsSize
						if isempty( buffer )
							initialize();
						else
							syncs = [ syncs buffer ];
							syncsSize = syncsSize + bufferSize;
						end
					end
					
					nbSyncs = nbSyncs + 1;
					syncs( nbSyncs ) = sync;
					
					varargout = { getParents( sync.inid ), getChildren( sync.outid ) };
					
				case 'depends'
					
					varargout = { getChildren( varargin{1} ) };
					
				case 'rem'
					
					ind = syncs == varargin{1};
					if any( ind )
						syncs( ind ) = [];
						nbSyncs = nbSyncs - nnz( ind );
						syncsSize = syncsSize - nnz( ind );
					end
					
					varargout = {};
					
				case 'remall'
					
					for i = 1:nbSyncs
						delete( syncs(i) );
					end
					
					buffer = [];
					syncs = [];
					syncsSize = 0;
					nbSyncs = 0;
					
			end
			
			function parents = getParents( idprop )
				ind = cellfun( @(ids) any( ismember( idprop, ids ) ), { syncs( 1:nbSyncs ).outid } );
				parents = syncs( ind );
			end
			function children = getChildren( idprop )
				ind = cellfun( @(ids) any( ismember( idprop, ids ) ), { syncs( 1:nbSyncs ).inid } );
				children = syncs( ind );
			end
			
			recursiveFlag = false;
			
		end
		
	end
	
end

Contact us