cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 
‘New to using JMP? Hit the ground running with the Early User Edition of Discovery Summit – register now, free of charge.
Register for our Discovery Summit 2024 conference, Oct. 21-24, where you’ll learn, connect, and be inspired.
Choose Language Hide Translation Bar
miguello
Level VI

How to prevent cross-talking between instances of the same script (or only allow one instance)?

All, 

 

I have a following problem. I have a script that opens\closes multiple tables and windows, and I create several namespaces to handle this. I also delete those namespaces in <<On Close() of the main window.

 

I found that when I have two instances of the script running, closing main window in one of them deletes the namespaces and breaks the other script.

How do I make namespaces separate for each instance or, alternatively, how do I only allow one instance of the script\add-in running?

 

I understand that in

ns = New Namespace();

ns is a reference, and there is a unique name, like "#43" that is assigned every time a namespace is created. I can get that name by <<Get Name() and thus point to a specific and unique namespace, but how can I make it work in multiple instances? Like, ns in one running script instance is referring to "#43", and ns in another running instance is referring to, let's say, "#44"?

 

 

 

5 REPLIES 5
jthi
Super User

Re: How to prevent cross-talking between instances of the same script (or only allow one instance)?

Depends on application. You could for example:

  • Check if specific window name exists
  • Use named namespaces and check if those already exist

and then perform do some action based on if they exist or don't.

Scripting Guide > Programming Methods > Advanced Scoping and Namespaces (help, jmp.com) is fairly good read

 

Names Default To Here(1);
// Delete Namespaces()

ns1 = New Namespace("AA");
show(ns1);
ns1 = New Namespace();
ns3 = New Namespace();
show(Get Namespace Names());

show(ns1);
If(Contains(Get Namespace Names(), "AA"),
	ns1 = Namespace("AA");
);
show(ns1);

If(Contains(Get Window List() << Get Window Title, "window_title"),
	Window("window_title") << Bring Window To Front;
	Throw("Window already exists...");
);
-Jarmo
ErraticAttack
Level VI

Re: How to prevent cross-talking between instances of the same script (or only allow one instance)?

Doing proper scope control and resource management in JMP can be tricky -- the JMP objects DataTable(), and Namespace() are only ever explicitly released from memory and never by dereference.

 

For my purposes I have created a meta-programming library that can handle all the scope management and resource control necessary (as it is part of my work I cannot share the files), but something a little like it is here:

 

::modify display tree functions = Function( {self, display tree},
	{Default Local},
	box types = {"TextEditBox", "ButtonBox", "ListBoxBox"}; // Add others here
	For( i = 1, i <= N Items( box types ), i++,
		boxes = display tree << XPath( "//" || box types[i] );
		For( j = 1, j <= N Items( boxes ), j++,
			box = boxes[j];
			function = Parse( Char( box << Get Function ) );
			If( Try( Is Missing( function ), 0 ), Continue() ); // no function set
			If( Head Name( function ) != "Function",
				arg = Name Expr( function );
				If( Head Name( arg ) != "Glue", // function is the right type -- a single call
					new arg = Eval Expr( Glue( self = Namespace( Expr( self << Get Name ) ) ) );
					Insert Into( new arg, Parse( "self:" || Char( Name Expr( arg ) ) ) );
					Eval( Substitute( Expr(
							box << Set Function( Local( {self}, __expr__ ) )
						)
					,
						Expr( __expr__ ), Name Expr( new arg )
					) )
				)
			,
				arg = Arg( function, N Arg( function ) );
				If( Head Name( arg ) != "Glue", // function is the right type -- a single call
					new arg = Eval Expr( Glue( self = Namespace( Expr( self << Get Name ) ) ) );
					Insert Into( new arg, Parse( "self:" || Char( Name Expr( arg ) ) ) );
					Eval( Substitute( Expr(
						box << Set Function(
							Function( __args__,
								{self},
								__expr__
							)
						)
					),
						Expr( __args__ ), Arg( function, 1 )
					,
						Expr( __expr__ ), Name Expr( new arg )
					) )
				)
			);
		)
	)
);

::inject self namespace = Function( {script},
	{Default Local},
	self = New Namespace();
	script = Arg( script, 1 );
	windows = {};
	For( i = 1, i <= N Arg( script ), i++,
		arg i = Arg( script, i );
		If( Head Name( arg i ) == "Assign",
			Remove From( script, i );
			Insert Into( script,
				Substitute(
					Expr( Assign( _a_, _b_ ) )
				,
					Expr( _a_ ), Parse( "self:" || Char( Arg( arg i, 1 ) ) )
				,
					Expr( _b_ ), Arg( arg i, 2 )
				)
			,
				i
			);
			arg i = Arg( script, i )
		);
		If( Head Name( arg i ) == "Assign" & As Name( Head Name( Arg( arg i, 2 ) ) ) == As Name( "New Window" ),
			Insert Into( windows, Char( Arg( arg i, 1 ) ) )
		);
		If( Head Name( arg i ) == "Assign" & Head Name( Arg( arg i, 2 ) ) == "Function",
			Remove From( script, i );
			Insert Into( script,
				Substitute( Expr(
					Assign( _arg1_, Function( _args_,
						{Default Local},
						_script_
					) )
				),
					Expr( _arg1_ ), Arg( arg i, 1 )
				,
					Expr( _args_ ), Arg( Arg( arg i, 2 ), 1 )
				,
					Expr( _script_ ), If( Head Name( Arg( Arg( arg i, 2 ), N Arg( Arg( arg i, 2 ) ) ) ) == "Glue",
						Insert( Arg( Arg( arg i, 2 ), N Arg( Arg( arg i, 2 ) ) ),
							Eval Expr( self = Namespace( Expr( self << Get Name ) ) ),
							1
						)
					,
						Eval Expr(
							Glue(
								self = Namespace( Expr( self << Get Name ) ),
								Expr( Arg( Arg( arg i, 2 ), N Arg( Arg( arg i, 2 ) ) ) )
							)
						)
					)
				)
			,
				i
			)
		)
	);
	Insert Into( script, 0 );
	Eval( script );
	For( w = 1, w <= N Items( windows ), w++,
		win = Eval( Parse( windows[w] ) );
		::modify display tree functions( self, win );
		Eval( Eval Expr(
		win << On Close(
			Local( {self = Namespace( Expr( self << Get Name ) )},
				Try( self:on close );
				Delete Namespaces( Force( 1 ), self )
			)
		)
		) )
	);
	self
);

script = {
	a = 1;
	__tables = {};
	
	add table = Function( {_table},
		Insert Into( self:__tables, _table )
	);
	
	close tables = Function( {},
		For( i = 1, i <= N Items( self:__tables ), i++,
			Try( Close( self:__tables[i], No Save ) )
		)
	);
		
	func = Function( {b},
		Show( b + self:a )
	);
	
	on close = Function( {},
		Print( "Closing!!" );
		self:close tables;
	);
	
	run button = Function( {},
		Print( "You pressed the button" );
		table = Open( "$SAMPLE_DATA/Big Class.jmp" );
		self:add table( table << Subset( All Rows, All Columns, Not Linked ) );
		Close( table, No Save );
	);
	
	win = New Window( "teest",
		List Box( {"A","B"}, <<Set Function( func( 3 ) ) )
	,
		List Box( {"1", "2"}, <<Set Function( Function( {this}, func( Try( Num( (Insert( {}, this << Get Selected ))[1] ), 0 ) ) ) ) )
	,
		Button Box( "Press Me", <<Set Function( run button ) )
	);
};

self = ::inject self namespace( script );
self = ::inject self namespace( script );

If you've already loaded the two global functions then you can keep them out of the script -- they serve to modify the functions within the namespace to be aware of which namespace they're in, as well as to modify the <<Set Function( ... ) methods of the display tree to call the relevant functions within the scoping namespace.  Also, this will automatically set the  <<On Close() method of the window to run the self:on close() function (if it exists).

 

Note that this will, by default, delete the scoping self namespace when the window closes.  This is not always the desired behavior, but for this simple demo it will suffice.

 

The last two lines of the script each open a window, but there is no cross-talk between them (you can load some tables by pressing the button, and when the window is closed the tables are automatically closed as well)

Jordan
ih
Super User (Alumni) ih
Super User (Alumni)

Re: How to prevent cross-talking between instances of the same script (or only allow one instance)?

Hie @miguello,

 

As you pointed out you need the variable ns to be unique to each script, not just the namespace.  @jthi solves this using Names Default to here(1).  To define this explicitly, and perhaps better understand how it works, try running these two lines in a few different windows, 

 

here:ns = New Namespace();
here:ns:a = random normal();

 

and then use

show namespaces()

to see see the value of a in each namespace. Calling

here:ns:a

from each individual window will return that window's value.  In another window, run this:

Names default to here(1);
ns = New Namespace();
ns:a = random normal();

and note how it behaves the same way as the previous example. Try that without the here namespace and all reference to a will return the same value, indicating that they are from the same namespace.

 

So, just putting

 

Names default to here(1);

at the top of your script might fix your issue.  You also might be able to avoid creating new named namespaces entirely by putting all of those variables you had there right in the here namespace instead.

 

vince_faller
Super User (Alumni)

Re: How to prevent cross-talking between instances of the same script (or only allow one instance)?

When you say instances, do you mean that you're doing multiple includes?  Or like a function?  

 

Names default to here(1);
create_instance = function({ns}, 

	Eval(EvalExpr(
		nw = new window("Test", 
			window:ns = namespace(Expr(ns << Get name()));
			<< On Close(
				Delete Namespaces(force(1), window:ns);
			),
			Buttonbox("Press Me",
				print(window:ns << Get name());
				show(window:ns:x, window:ns:y);
			)
		)
	));
);
create_instance(ns1 = new namespace({x=14;y=28}));
create_instance(ns2 = new namespace({x=140;y=280}));

This works for me.  

 

*Edit* actually it's even easier.  You don't even need the eval expr because it's not a runtime thing.  

 

create_instance = function({ns}, 
	nw = new window("Test", 
		window:ns = ns;
		<< On Close(
			Delete Namespaces(force(1), window:ns);
		),
		Buttonbox("Press Me",
			print(window:ns << Get name());
			show(window:ns:x, window:ns:y);
		)
	)
);
create_instance(ns1 = new namespace({x=14;y=28}));
create_instance(ns2 = new namespace({x=140;y=280}));
Vince Faller - Predictum
mikedriscoll
Level VI

Re: How to prevent cross-talking between instances of the same script (or only allow one instance)?

To add to ih's comments:

One thing I have noticed is that if you have a script file open with 'names default to here(1)' at the top, and run it to get your first output, then run it again to get a second output, then the first output has elements that are broken because the variables were replaced by those in the second output. 

 

But if you take that exact same script, and call it from an add-in, that problem does not happen. The two report windows function independently as expected. I set up my add-ins in toolbars. You might be able to skip the addin and just add an item to the toolbar, and have that item call your jsl file.  This is all without any other namespace for this (other than one that i use for implementing a 'recall' feature for the GUI). I don't use application builder; I build my add-ins manually, and so mine don't have the 'application' namespace that gets added automatically with the application builder. Don't know if that matters.

 

This may or may not work for you, I'm not sure exactly what your code is doing. But I wonder if you can eliminate all of the namespaces and just put 'names default to here(1)' at the top, and then run the script from the menu instead of ctrl+R.  Note that when I run my script, I never have two scripts 'running' at the same time, I run them consecutively and then when the second one completes, both outputs are open at the same time.

 

This may cause issues with debugging though, because you would be debugging and running the script while open (not through the toolbar icon) which means you can only work with the latest output.