Highlighted

Update main After Effects UI from custom UI thread

Explorer ,
Aug 14, 2020

Copy link to clipboard

Copied

Hi. I am running  a custom UI in a separate thread. The custom UI sets a global counter when the user clicks on Update button.

Then I call, in the custom UI thread

AEGP_SuiteHandler suites(sP);
suites.AdvAppSuite2()->PF_RefreshAllWindows();

the sP value is set in GlobalSetup as sP = in_data->pica_basicP;

The plan is that I can use the global counter to update a hidden param in UpdateParameterUI to force render.

However, the app is crashing on the line

suites.AdvAppSuite2()->PF_RefreshAllWindows();

 

Any ideas ?

 

TOPICS
SDK

Views

92

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more

Update main After Effects UI from custom UI thread

Explorer ,
Aug 14, 2020

Copy link to clipboard

Copied

Hi. I am running  a custom UI in a separate thread. The custom UI sets a global counter when the user clicks on Update button.

Then I call, in the custom UI thread

AEGP_SuiteHandler suites(sP);
suites.AdvAppSuite2()->PF_RefreshAllWindows();

the sP value is set in GlobalSetup as sP = in_data->pica_basicP;

The plan is that I can use the global counter to update a hidden param in UpdateParameterUI to force render.

However, the app is crashing on the line

suites.AdvAppSuite2()->PF_RefreshAllWindows();

 

Any ideas ?

 

TOPICS
SDK

Views

93

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Aug 14, 2020 0
Explorer ,
Aug 14, 2020

Copy link to clipboard

Copied

@shachar I'll post some notes from your replies to similar questions. It would be nice to have an example of a custom UI thread and how it interacts with main Adobe app.

 

in general, the AE API is not thread safe. there are very few calls that are, and they are noted so in the docs. (such as AEGP_CauseIdleRoutinesToBeCalled)

you can only call AE when AE is actually listening, which means either from the ui thread, render thread, or in some occasions, other threads that are created by the iterations suites, but only while executing that suite call from one of the main threads...

lookup AEGP_CheckoutOrRender_ItemFrame_AsyncManager in the docs. you'll either have to adapt your process to that mechnism, or get your frame data from one of the main thread,s and the do you processing on whatever thread you like without calling AE from it.

AND

there are some very few calls that can by made asynchronously. most of

which are rendering related calls that are indeed multi-threaded but are

all executed during a single render call.

the one exception is AEGP_CauseIdleRoutinesToBeCalled(), which is safe to

call from any thread. all other attempts to talk to AE from a custom thread

instead of one of AE's calls will result in catastrophic crashes...

to do what you describe, you should probably use idle_hook to execute your

changes, and instead of storing an AEGP_EffectRef, you should store your

effect's index on the layer, layer id and comp item id. then during idle

hook you lookup the comp, the layer in the comp and the effect on the

layer, to get a fresh AEGP_EffectRefH for usage during that idle hook call.

all other item refs such as AEGP_LayerH and AEGP_CompH are not guaranteed

to keep beyond the scope of a single AE call. (sometimes they do, but often

they don't)

AND

1. AEGP_CauseIdleRoutinesToBeCalled(). it can be called from the non-main

thread (as long as you acquire it from the main thread), and causes idle

hook to be called ASAP. this can shorten your interactive time lag.

2. create your window as an AE panel (see the "panelator" sample project).

i think panels can interact with AE's API during mouse

down/click/draw/ect...

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 14, 2020 0
Adobe Community Professional ,
Aug 14, 2020

Copy link to clipboard

Copied

welp, that pretty much sums it up...

i'd just add that it's perfectly fine to do ui work in a separate thread, just do the actual ui update on the designated AE evetns. during said event, access your separete thread's data (probably using some mutex). that's it.

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 14, 2020 0
Explorer ,
Aug 14, 2020

Copy link to clipboard

Copied

To make sure I understood this...

In EntryPointFunc() add,

case PF_Cmd_COMPLETELY_GENERAL:
			err = RespondtoAEGP(in_data,
				out_data,
				params,
				output,
				extra);

Example of RespondToAEGP

static PF_Err
RespondtoAEGP(
	PF_InData* in_data,
	PF_OutData* out_data,
	PF_ParamDef* params[],
	PF_LayerDef* output,
	void* extraP)
{
	PF_Err			err = PF_Err_NONE;
	AEGP_SuiteHandler suites(in_data->pica_basicP);

	// Get data from extraP if needed, else just use shared static var with mutex
        // Make changes to Adobe stuff here
	return err;
}

And in the custom UI thread add, use static var sP, previously set in GlobalSetup - sP = in_data->pica_basicP;

AEGP_SuiteHandler		suites(sP);
suites.UtilitySuite6()->AEGP_CauseIdleRoutinesToBeCalled();

Is that all correct ?

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 14, 2020 0
Explorer ,
Aug 14, 2020

Copy link to clipboard

Copied

I'm missing something basic here. My breakpoint hits on the custom UI thread at

 

suites.UtilitySuite6()->AEGP_CauseIdleRoutinesToBeCalled();

 

but the callback in main thread i.e.

 

RespondtoAEGP

 

is not called.

Is there a flag or another variable to set/method to be called? Inside RespondtoAEGP, I set a param and refresh Adobe UI in this manner.

 

params[HIDDEN_ID]->u.fs_d.value = uiData.clicks % 100ull; // So hidden param is updated
	params[HIDDEN_ID]->uu.change_flags = PF_ChangeFlag_CHANGED_VALUE;
	ERR(suites.ParamUtilsSuite3()->PF_UpdateParamUI(in_data->effect_ref, HIDDEN_ID, params[HIDDEN_ID]));
	my_sequence_dataP seqP = *(my_sequence_dataP*)out_data->sequence_data;

	// SequenceResetup may get called after this and before render. Not a good idea as some of our data will be deleted.
// paramflag sets SequenceResetup not to delete sequence data
	seqP->paramflag = PF_OutFlag_FORCE_RERENDER;
	out_data->out_flags |= PF_OutFlag_FORCE_RERENDER;

 

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 14, 2020 0
Adobe Community Professional ,
Aug 14, 2020

Copy link to clipboard

Copied

what is the UI thing you're doing? is it just conditionally changing param visibility, or is it a custom param ui/comp ui?

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 14, 2020 0
Explorer ,
Aug 14, 2020

Copy link to clipboard

Copied

I am reading in from a 5x5 matrix with certain presets. I have an Update button also. When the user clicks Update, the matrix contents are saved into a uiData struct.

I want to force render when the Update is pressed. So in the Update callback I have the

AEGP_CauseIdleRoutinesToBeCalled

However, this is not triggering the

PF_Cmd_COMPLETELY_GENERAL

Once its triggered, its easy as I have a static struct that can be accessed by render thread. For the moment I am not worried about race conditions.

I found another thread with a similar issue -

 

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 14, 2020 0
Adobe Community Professional ,
Aug 14, 2020

Copy link to clipboard

Copied

AEGP_CauseIdleRoutinesToBeCalled causes idle_hook to be called, not PF_Cmd_COMPLETELY_GENERAL. am i misundertsnaindg something here?

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 14, 2020 0
zcream LATEST
Explorer ,
Aug 15, 2020

Copy link to clipboard

Copied

I was using the incorrect methods - IdleHook is clearly the wat to go. I read up more on this and I am still having problems with IdleHook. Essentially, IdleHook is not being called.

 

I pass this struct to IdleHook.

/* instead of storing an AEGP_EffectRef, you should store your effect's index on the layer, layer id and comp item id. then during idle hook you lookup the comp, the layer in the compand the effect on the layer, to get a fresh AEGP_EffectRefH for usage during that idle hook call.
*/
typedef struct  {
	AEGP_LayerH         layerH; // layer
	AEGP_EffectIndex    effect_indexL;
	AEGP_CompH compH;
 } IdleHookInfo_t;

Inside GlobalSetup, I obtain these values

sP = in_data->pica_basicP;

if (in_data->appl_id != 'PrMr') {
	// This is only needed for the AEGP suites, which Premiere Pro doesn't support
	ERR(suites.UtilitySuite3()->AEGP_RegisterWithAEGP(NULL, Product_MatchName, &S_my_id));

	AEGP_SuiteHandler suites(in_data->pica_basicP);
	A_long				num_effectsL;
	A_char				effect_nameAC[AEGP_MAX_ITEM_NAME_SIZE] = { '\0' };
	AEGP_ItemH          active_itemH = NULL;
	AEGP_ItemType       item_type = AEGP_ItemType_NONE;
	AEGP_EffectRefH effectH;
	IdleHookInfo_t		*hookInfo = new (IdleHookInfo_t);

	ERR(suites.ItemSuite8()->AEGP_GetActiveItem(&active_itemH));
	if (active_itemH)
	  ERR(suites.ItemSuite8()->AEGP_GetItemType(active_itemH, &item_type));
	  if (!err) {
	    if (AEGP_ItemType_COMP == item_type) {
		A_long layer_indexL = 0;
		ERR(suites.CompSuite4()->AEGP_GetCompFromItem(active_itemH, &hookInfo->compH));
		ERR(suites.LayerSuite5()->AEGP_GetCompLayerByIndex(hookInfo->compH, layer_indexL, &hookInfo->layerH));
		ERR(suites.EffectSuite2()->AEGP_GetLayerNumEffects(hookInfo->layerH, &num_effectsL));
                for (A_long jL = 0; !err && jL < num_effectsL; jL++) {
                  ERR(suites.EffectSuite2()->AEGP_GetLayerEffectByIndex(S_my_id, hookInfo->layerH, jL, &effectH));
                  if (!err && effectH) {
	            AEGP_InstalledEffectKey key;
	            ERR(suites.EffectSuite2()->AEGP_GetInstalledKeyFromLayerEffect(effectH, &key));
	            ERR(suites.EffectSuite2()->AEGP_GetEffectName(key, effect_nameAC));

	            if (!strcmp(effect_nameAC, "Convolution")) {
	              hookInfo->effect_indexL = jL;
	              AEGP_IdleRefcon idleRefcon = reinterpret_cast<AEGP_IdleRefcon>(&hookInfo);
	              return(suites.RegisterSuite5()->AEGP_RegisterIdleHook(S_my_id, IdleHook, idleRefcon));
						}}}}}}

Here is my IdleHook

PF_Err IdleHook(AEGP_GlobalRefcon plugin_refconP, AEGP_IdleRefcon refconP, A_long* sleepL) {

	IdleHookInfo_t *hookInfo = reinterpret_cast<IdleHookInfo_t *>(refconP);
	AEGP_SuiteHandler	suites(sP);
	A_Err				err = A_Err_NONE;
	AEGP_StreamRefH		stream_refH;
	A_Time				timeT = { 0,1 };
	AEGP_EffectRefH effectH;
	
	ERR(suites.EffectSuite4()->AEGP_GetLayerEffectByIndex(S_my_id, hookInfo->layerH, hookInfo->effect_indexL, &effectH));
	ERR(suites.StreamSuite2()->AEGP_GetNewEffectStreamByIndex(S_my_id, effectH, HIDDEN, &stream_refH));
	AEGP_StreamValue2 val = { stream_refH, uiData.clicks % 100ull };
	ERR(suites.StreamSuite3()->AEGP_SetStreamValue(S_my_id, stream_refH, &val));

	return err;
}

 

Likes

Translate

Translate

Report

Report
Community Guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
Reply
Loading...
Aug 15, 2020 0