NDK学习笔记(三):DynamicKnobs的机制

最近的NDK开发涉及到了动态input及动态knobs的问题。

开发需求如下:建立一个节点,该节点能获取每一个input上游的inputframerange信息。

具体下来就是:需要Node的input可以不断增加,而不是固定的几个;而knobs的数量也与input数量同步。

查了nuke提供的开发工具,动态input数量的问题已经解决了,本文主要谈DynamicKnobs的机制。

DynamicKnobs.cpp的机制相对较复杂,涉及到三个方面:

一:

void DynamicKnobs::knobs(Knob_Callback f)
{
  Bool_knob(f, &_showDynamic, "show_dynamic", "Show Dynamic");     //add a bool knob
  SetFlags(f, Knob::KNOB_CHANGED_ALWAYS);    //give it a description

  //If you were creating knobs by default that would be replaced, this would need to 
  //be called to create those. 
  //_numNewKnobs = add_knobs(addDynamicKnobs, this->firstOp(), f);    

  //Call the callback manually this once to ensure script loads have the appropriate knobs to load into.
  if(!f.makeKnobs())
    DynamicKnobs::addDynamicKnobs(this->firstOp(), f);
}

首先需要理清什么是Knob,在ndk文档中有明确解释,原文如下:

Knobs in NUKE manage the UI elements seen in the param panel and viewer opengl handles, along with the data storage responsible for reading and writing to the NUKE script file。

译文如下:

Nuke中的Knobs管理参数面板中的UI元素和viewer中的opengl操作手柄,跟数据存储机制一起负责读写Nuke工程文件。

理清knobs的功能之后,我们再来研究这段代码。

在这段代码中我们看到先建立了一个名为show_dynamic的Bool_knob属性,下一行利用了SetFlags方法设置了一个很重要的标记,该标记表示该行之后建立的knob都会被视作动态knob,后来添加的knob都会被添加到该Flag之后。
在往下看,add_knobs会在该节点初始化的时候就调用addDynamicKnobs这个callback来建立knob,这个knob会被建立在firstOp()上,即第一个Op实例上,当然Knob_Callback依然还是f。add_knobs方法的返回值是通过addDynamicKnobs所建立起knob的数量。该返回值会存储在_newNewKnobs上。

if(!f.makeKnobs())的意思是判断当前是否有建立knob的行为,如果没有这个建立knob的行为,就会自动调用addDynamicKnobs方法。

通过以上代码就会建立一个自动创建knob并区分哪些是新knob那些是必须保留的机制。

实际上我们也发现了,Knobs是通过callbacks来与Nuke中其他组件进行交互访问的。理解这一点至关重要。

二:

void DynamicKnobs::addDynamicKnobs(void* p, Knob_Callback f)    //click the bool knob,show the slide knob.
{
  if(((DynamicKnobs*)p)->getShowDynamic()) {
    Float_knob(f, ((DynamicKnobs*)p)->getDynamicKnobStore(), "dynamic_knob", "Dynamic Knob");
  }   
}

 该方法非常简单,getShowDynamic可以获得show_dynamic这个knob的当前值,如果为真,就会创建一个新的knob。该方法会在下一个方法中被调用。

三:

int DynamicKnobs::knob_changed(Knob* k)
{
  if(k==&Knob::showPanel || k->is("show_dynamic")) {
    _numNewKnobs = replace_knobs(knob("show_dynamic"), _numNewKnobs, addDynamicKnobs, this->firstOp());
    return 1;
  }
  return NoIop::knob_changed(k);
}       

关于knob_changed(Knob* k)方法ndk开发文档中也有解释,原文如下:

int Op::knob_changed(Knob* )[virtual]

Whenever the user moves a Knob this is called with a pointer to the Knob, on one of the Ops controlled by that Knob. The purpose is to automatically enable/disable or set the values of other knobs.

Do not assume this is called on all instances of your Op! It will not be in cases of clones or multiple frame numbers in Nuke. So storing results in this call is wrong! To get values out of knobs you must either rely on them being stored and validate() being called, or you must ask for them using knob(name)->value().

You must return non-zero if you do anything. Returning zero indicates that there is no need to call this on the same knob again. Base class returns zero always.

Reimplemented in DD::Image::DrawIop, DD::Image::GeoOp, DD::Image::NukeWrapper, and DD::Image::ReadGeo.

Referenced by DD::Image::NukeWrapper::knob_changed().

译文如下:

用户调整某knob参数的时候,指向该knob的指针会在一个被该knob控制的op上调用knob_changed方法。这个机制的目的是通过该knob来控制其他knob的值。

不要假定认为这个方法在所有op实例中被调用,在克隆和多帧动画的情况下是不会被调用的,即该方法只对当前帧当前op中的参数调整生效。所以也不要在这个方法中存储任何值,在没有knobs的情况你要获得值你确保knobs的值被存储还有validata()方法被调用。或者你也可以通过knob(name)->value()方法来获得knob的值。

无论做什么事都不要返回零值。返回零值就表示在这个knob上之后都不需要再调用knob_changed方法了。需要注意,这个方法的基类只返回零值。

通过这段解释,我们很容易了解knob_changed方法的机制,首先该方法需要一个Knob类的参数k,该方法会监听所有knob值改变的信息。所以需要通过if语句来确定一个条件,即当show_dynamic这个knob参数发生变化,或者knob在面板上显示的时候。当以上两个条件任意成立一个的时候就会触发内部代码块。首先调用replace_knobs()方法来调用addDynamicKnobs()方法。

在这里详细解释下replace_knobs()方法的机制,原文如下:

int Op::replace_knobs ( Knob afterthis,
    int  n,
    void(*)(void *, Knob_Callback)  f,
    void *  v,
    const char *  fileExt = 0 
  )    

Change the set of knobs this node has, by deleting n knobs after the afterthis knob, then inserting new knobs produced by calling the function f. The return value is the number of knobs created by f, which you probably want to save and pass to the next call.

If n is zero or negative then this only creates knobs. If f is null then this only destroys knobs.

Currently the old knobs are completly destroyed. Future versions may try to match up the new knobs with the old ones and preserve the values and widgets.

Your knobs() function should also call f by using add_knobs(). You also need to pick a knob that controls the rest of the knobs and set the Knob::KNOB_CHANGED_ALWAYS flag on it so that you can change the set of knobs on any changes.

译文如下:

通过删除afterthis之后的n个节点,再插入回调函数f创建的新knobs来改变当前Node的knobs集合。该函数会返回新创建knobs的数量。

如果n是零值或者负值,replace_knobs方法就只会创建knobs,如果回调函数f为空,那么replace_knobs就只会删除指定knobs。

一般来说旧的knobs被删除之后,新版本的的node会尝试将新knobs与旧knobs匹配起来,确保值与窗口属性等保持一致。

你的knobs方法的定义中也应该用add_knobs()方法去调用f,你也需要选择一个knob来控制其他knobs并且设置一个KNOB_CHANGED_ALWAYS flag来确保你能改变knobs集合。

通过这段解释就可以清楚的了解到replace_knobs的机制,当然show_dynamic这个knob值发生变化的时候,就会触发replace_knobs这个方法来删除show_dynamic这个knob之后_numNewKnobs数量的的knobs,并且调用addDynamicKnobs来添加新的节点,同时返回新添加的knobs数量,更新到_numNewKnobs中。然后返回1,表示当前执行成功。
如果if语句内部代码并没有被触发,就会返回knob_changed(k)方法,重复这个循环。

分析完上面三个关键的方法之后不难发现,DynamicKnobs的机制很简单,即:knob_changed()调用了replace_knobs(),replace_knobs()调用了addDynamicKnobs(),通过这种方式去添加新功能。

首先需要建立一个knobs()方法来定义基本的knob,并且保证定义至少中有KNOB_CHANGED_ALWAYS这样一个flag。

第二:定义一个添加knobs的方法即addDynamicKnobs(),内部可以包含一个条件语句,一旦满足条件就添加knob。

第三,需要定义一个监听方法即knob_changed()来获得knobs值的状态,是否这些knobs记录的值发生了变化,一旦指定的knob的值发生了变化,就立刻调用replace_knobs()方法来更新具体位置之后的knobs集合。replace_knobs会先删除一部分knobs,再调用addDynamicKnobs()创建新的knobs。

DynamicKnobs是一个非常结构性的node,通过对这个源码的学习也逐渐理解了C++的一些思想,即基础的去构建一个功能。

原文地址:https://www.cnblogs.com/hksac/p/5035798.html