Cocos2d-x 源码分析 : Scheduler(定时器) 源码分析

源码版本 3.1r,转载请注明

我也终于不out了,开始看3.x的源码了,此时此刻的心情只能是wtf!!!!!!!!!!不过也终于告别CC时代了。

cocos2d-x 源码分析目录

http://blog.csdn.net/u011225840/article/details/31743129

1.继承结构

没错,是两张图。(你没有老眼昏花。。我脑子也没有秀逗。。)Ref就是原来的CCObject,而Timer类是与Scheduler类密切相关的类,所以需要把他们放在一起说。Timer和Scheduler的关系就像Data和DataManager的关系。

2.源码分析

2.1 Timer

2.1.1 Timer中的数据

Timer类定义了一个行为执行的间隔,执行的次数等,可以理解为定时器的数据类,而具体的定时器的行为,定义在子类中。Timer中的数据如下:

//_elapsed 上一次执行后到现在的时间
	//timesExecuted 执行的次数
	//interval 执行间隔
	//useDelay 是否使用延迟执行
    float _elapsed;
    bool _runForever;
    bool _useDelay;
    unsigned int _timesExecuted;
    unsigned int _repeat; //0 = once, 1 is 2 x executed
    float _delay;
    float _interval;

2.1.2 Update函数

void Timer::update(float dt)
{

	//update方法使用的是模板设计模式,将trigger与cancel的实现交给子类。

    if (_elapsed == -1)
    {
        _elapsed = 0;
        _timesExecuted = 0;
    }
	//四种情况
	/*
		1.永久执行并且不使用延迟:基本用法,计算elapsed大于interval后执行一次,永不cancel。
		2.永久执行并且使用延迟:当elapsed大于延迟时间后,执行一次后,进入情况1.
		3.不永久执行并且不使用延迟:情况1结束后,会判断执行次数是否大于重复次数,大于后则cancel。
		4.不永久执行并且使用延迟:情况2结束后,进入情况3.
	*/
    else
    {
        if (_runForever && !_useDelay)
        {//standard timer usage
            _elapsed += dt;
            if (_elapsed >= _interval)
            {
                trigger();

                _elapsed = 0;
            }
        }
        else
        {//advanced usage
            _elapsed += dt;
            if (_useDelay)
            {
                if( _elapsed >= _delay )
                {
                    trigger();

                    _elapsed = _elapsed - _delay;
                    _timesExecuted += 1;
                    _useDelay = false;
                }
            }
            else
            {
                if (_elapsed >= _interval)
                {
                    trigger();

                    _elapsed = 0;
                    _timesExecuted += 1;

                }
            }

            if (!_runForever && _timesExecuted > _repeat)
            {    //unschedule timer
                cancel();
            }
        }
    }
}

正如我注释中所说,update使用了模板方法的设计模式思想,将trigger与cancel调用的过程写死,但是不同的子类实现trigger和cancel的方式不同。

另外需要注意的是,Schedule使用时delay的需求,当有delay与没有delay我在源码中已经分析的很清楚了。

2.2 TimerTargetSelector   && TimerTargetCallback

前者是针对类(继承自Ref)中的method进行定时,而后者是针对function(普通函数)。

前者绑定的类型是SEL_SCHEDULE(你问我这是什么?)typedef void (Ref::*SEL_SCHEDULE)(float);一个指向Ref类型的method指针,并且该method必须满足参数是float,返回值是void。后者绑定的类型是ccSchedulerFunc---------typedef std::function<void(float)> ccSchedulerFunc;这是虾米?这是c++11的新特性,其实就是一个函数指针。

从他们实现的trigger方法中可以更好的看清这一切。

void TimerTargetSelector::trigger()
{
    if (_target && _selector)
    {
        (_target->*_selector)(_elapsed);
    }
}

void TimerTargetCallback::trigger()
{
    if (_callback)
    {
        _callback(_elapsed);
    }
}

最后说一下,TargetCallback中含有一个key,而前者没有。这在下面的源码分析中会看到。(其实原理很简单,SEL_SCHEDULE可以当成key,ccSchedulerFunc不能,因为前者有唯一的标识,如果你不懂这点,欢迎去复习下c++的指向类中方法的函数指针)

    Ref* _target;
    SEL_SCHEDULE _selector;
 ------  ------------------------
    void* _target;
    ccSchedulerFunc _callback;
    std::string _key;

2.3 Scheduler

2.3.1 Schedule && UnSchedule

Schedule有四种重载方法。其中各有两种针对不同的Timer子类,但是都大同小异,在此之前,不得不说一个用的非常多的数据结构tHashTimerEntry

typedef struct _hashSelectorEntry
{
    ccArray             *timers;
    void                *target;
    int                 timerIndex;
    Timer               *currentTimer;
    bool                currentTimerSalvaged;
    bool                paused;
    UT_hash_handle      hh;
} tHashTimerEntry;

这用到了开源库uthash,关于该hast的具体用法。请自行谷歌。UT_hash_handle能让我们根据key值找到相应的数据。在这个结构里,target是key值,其他都是数据(除了hh哦)。timers存放着该target相关的所有timer。currentTimerSalvaged的作用是如果你想停止unschedule正在执行的timer时,会将其从timers移除,并retain,防止被自动回收机制回收,然后将此标识为true。下面来看下第一种TimerCallback的Schedule。

void Scheduler::schedule(const ccSchedulerFunc& callback, void *target, float interval, unsigned int repeat, float delay, bool paused, const std::string& key)
{
    CCASSERT(target, "Argument target must be non-nullptr");
    CCASSERT(!key.empty(), "key should not be empty!");
	//先在hash中查找该target(key值)是否已经有数据
    tHashTimerEntry *element = nullptr;
    HASH_FIND_PTR(_hashForTimers, &target, element);
	//没有就创建一个,并且将其加入
    if (! element)
    {
        element = (tHashTimerEntry *)calloc(sizeof(*element), 1);
        element->target = target;

        HASH_ADD_PTR(_hashForTimers, target, element);

        // Is this the 1st element ? Then set the pause level to all the selectors of this target
        element->paused = paused;
    }
    else
    {
        CCASSERT(element->paused == paused, "");
    }

	//第一次创建target的数据,需要将timers初始化
    if (element->timers == nullptr)
    {
        element->timers = ccArrayNew(10);
    }
    else
    {
		//在timers中查找timer,看在该target下的所有timer绑定的key值是否存在,如果存在,设置新的interval后返回。
		//这里必须要解释下,target是hash表的key值,用来查找timers等数据。
		//而TimerCallback类型的timer本身含有一个key值(std::string类型),用来标识该唯一timer
        for (int i = 0; i < element->timers->num; ++i)
        {
            TimerTargetCallback *timer = static_cast<TimerTargetCallback*>(element->timers->arr[i]);

            if (key == timer->getKey())
            {
                CCLOG("CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer->getInterval(), interval);
                timer->setInterval(interval);
                return;
            }
        }
        ccArrayEnsureExtraCapacity(element->timers, 1);
    }
	//如果TimerCallback原本不存在在timers中,就添加新的
    TimerTargetCallback *timer = new TimerTargetCallback();
    timer->initWithCallback(this, callback, target, key, interval, repeat, delay);
    ccArrayAppendObject(element->timers, timer);
    timer->release();
}

TimerTargetSelector的Schedule不需要本身在通过key值进行存取。其他部分都与上面相同,唯独在查找是否存在Timer时,直接使用了selector。

 if (selector == timer->getSelector())
            {
                CCLOG("CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %.4f to %.4f", timer->getInterval(), interval);
                timer->setInterval(interval);
                return;
            }

继续看下TimerTargetSelector的unschedule。

void Scheduler::unschedule(SEL_SCHEDULE selector, Ref *target)
{
    // explicity handle nil arguments when removing an object
    if (target == nullptr || selector == nullptr)
    {
        return;
    }

    //CCASSERT(target);
    //CCASSERT(selector);

    tHashTimerEntry *element = nullptr;
    HASH_FIND_PTR(_hashForTimers, &target, element);
    //如果该target存在数据,就进行删除操作。
    if (element)
    {
		//遍历寻找
        for (int i = 0; i < element->timers->num; ++i)
        {
            TimerTargetSelector *timer = static_cast<TimerTargetSelector*>(element->timers->arr[i]);
            //如果正在执行的Timer是需要被unschedule的timer,将其移除并且标识当前正在执行的Timer需要被移除状态为true。
            if (selector == timer->getSelector())
            {
                if (timer == element->currentTimer && (! element->currentTimerSalvaged))
                {
                    element->currentTimer->retain();
                    element->currentTimerSalvaged = true;
                }

                ccArrayRemoveObjectAtIndex(element->timers, i, true);

                // update timerIndex in case we are in tick:, looping over the actions
                if (element->timerIndex >= i)
                {
                    element->timerIndex--;
                }

				//当前timers中不再含有timer。但是如果正在执行的target是该target,则将正在执行的target将被清除标识为true
				//否则,可以直接将其从hash中移除
                if (element->timers->num == 0)
                {
                    if (_currentTarget == element)
                    {
                        _currentTargetSalvaged = true;
                    }
                    else
                    {
                        removeHashElement(element);
                    }
                }

                return;
            }
        }
    }
}

同理反观TimerTargetCallback,查找时需要用到std::string,这里不再赘述。

2.3.2 Scheduler的两种定时模式

Scheduler允许有两种定时模式:

1.带有interval(间隔)的定时模式,哪怕interval是0.(普通函数)

2.不带有interval的定时模式,即在每一帧更新之后都会调用到,会将一个类的update函数放入定时器。(此外,模式2还引入了优先级的概念)

从实现的源代码来看,如果你有一个需要每帧更新都需要调用的function or method,请一定将该部分放入类中的update函数后使用模式2来定时。因为每个模式2绑定了一个hash表能快速存取到,提高性能。上面一小节介绍的是如何添加和删除模式1的定时,下面看一下模式2.

  template <class T>
    void scheduleUpdate(T *target, int priority, bool paused)
    {
        this->schedulePerFrame([target](float dt){
            target->update(dt);
        }, target, priority, paused);
    }

别问我从哪里来,我tm来自c++11,如果不懂该写法,请自行谷歌c++11 lambda表达式。

具体开始分析SchedulePerFrame,在此之前,要先介绍两个数据结构。

// A list double-linked list used for "updates with priority"
typedef struct _listEntry
{
    struct _listEntry   *prev, *next;
    ccSchedulerFunc     callback;
    void                *target;
    int                 priority;
    bool                paused;
    bool                markedForDeletion; // selector will no longer be called and entry will be removed at end of the next tick
} tListEntry;

typedef struct _hashUpdateEntry
{
    tListEntry          **list;        // Which list does it belong to ?
    tListEntry          *entry;        // entry in the list
    void                *target;
    ccSchedulerFunc     callback;
    UT_hash_handle      hh;
} tHashUpdateEntry;

tListEntry,是一个双向链表,target是key,markedForDeletion来告诉scheduler是否需要删除他。tHashUpdateEntry是一个哈希表,通过target可以快速查找到相应的tListEntry。可以注意到,HashEntry中有个List,来表示该entry属于哪个list。在scheduler中,一共有三个updateList,根据优先级分为negativeList,0List,positiveList,值越小越先执行。

数据结构介绍完毕,可以开始介绍函数了。

void Scheduler::schedulePerFrame(const ccSchedulerFunc& callback, void *target, int priority, bool paused)
{
	//先检查hash中是否存在该target,如果存在,则将其deleteion的标识 置为false后返回。(可能某个操作将其置为true,并且
	//scheduler还没来得及删除,所以这里只需要再改为false即可)
    tHashUpdateEntry *hashElement = nullptr;
    HASH_FIND_PTR(_hashForUpdates, &target, hashElement);
    if (hashElement)
    {
#if COCOS2D_DEBUG >= 1
        CCASSERT(hashElement->entry->markedForDeletion,"");
#endif
        // TODO: check if priority has changed!

        hashElement->entry->markedForDeletion = false;
        return;
    }

    // most of the updates are going to be 0, that's way there
    // is an special list for updates with priority 0
	//英文注释解释了为啥有一个0List。
    if (priority == 0)
    {
        appendIn(&_updates0List, callback, target, paused);
    }
    else if (priority < 0)
    {
        priorityIn(&_updatesNegList, callback, target, priority, paused);
    }
    else
    {
        // priority > 0
        priorityIn(&_updatesPosList, callback, target, priority, paused);
    }
}
void Scheduler::appendIn(_listEntry **list, const ccSchedulerFunc& callback, void *target, bool paused)
{
	//为该target新建一个listEntry
    tListEntry *listElement = new tListEntry();

    listElement->callback = callback;
    listElement->target = target;
    listElement->paused = paused;
    listElement->markedForDeletion = false;

    DL_APPEND(*list, listElement);

    // update hash entry for quicker access
	//并且为该target建立一个快速存取的target
    tHashUpdateEntry *hashElement = (tHashUpdateEntry *)calloc(sizeof(*hashElement), 1);
    hashElement->target = target;
    hashElement->list = list;
    hashElement->entry = listElement;
    HASH_ADD_PTR(_hashForUpdates, target, hashElement);
}
void Scheduler::priorityIn(tListEntry **list, const ccSchedulerFunc& callback, void *target, int priority, bool paused)
{
	//同理,为target建立一个entry
    tListEntry *listElement = new tListEntry();

    listElement->callback = callback;
    listElement->target = target;
    listElement->priority = priority;
    listElement->paused = paused;
    listElement->next = listElement->prev = nullptr;
    listElement->markedForDeletion = false;

    // empty list ?
    if (! *list)
    {
        DL_APPEND(*list, listElement);
    }
    else
    {
        bool added = false;
		//根据优先级,将element放在一个合适的位置,标准的有序链表插入操作,不多解释。
        for (tListEntry *element = *list; element; element = element->next)
        {
            if (priority < element->priority)
            {
                if (element == *list)
                {
                    DL_PREPEND(*list, listElement);
                }
                else
                {
                    listElement->next = element;
                    listElement->prev = element->prev;

                    element->prev->next = listElement;
                    element->prev = listElement;
                }

                added = true;
                break;
            }
        }

        // Not added? priority has the higher value. Append it.
        if (! added)
        {
            DL_APPEND(*list, listElement);
        }
    }

    // update hash entry for quick access
    tHashUpdateEntry *hashElement = (tHashUpdateEntry *)calloc(sizeof(*hashElement), 1);
    hashElement->target = target;
    hashElement->list = list;
    hashElement->entry = listElement;
    HASH_ADD_PTR(_hashForUpdates, target, hashElement);
}

ok,到这里,我们已经明白update的定时是如何添加进来的,scheduler用了下面的成员来管理这些entry。

 //
    // "updates with priority" stuff
    //
    struct _listEntry *_updatesNegList;        // list of priority < 0
    struct _listEntry *_updates0List;            // list priority == 0
    struct _listEntry *_updatesPosList;        // list priority > 0
    struct _hashUpdateEntry *_hashForUpdates; // hash used to fetch quickly the list entries for pause,delete,etc

下面,继续分析源码,看一下是如何移除这些update的定时的。

void Scheduler::unscheduleUpdate(void *target)
{

    if (target == nullptr)
    {
        return;
    }

    tHashUpdateEntry *element = nullptr;
    HASH_FIND_PTR(_hashForUpdates, &target, element);
    if (element)
    {
        if (_updateHashLocked)
        {
            element->entry->markedForDeletion = true;
        }
        else
        {
            this->removeUpdateFromHash(element->entry);
        }
    }
}

代码简介易懂,唯一需要注意的地方是当updateHashLocked为true时,表示当前情况下不允许更改该hash表,只能先将其deletion标记为true。(在执行update的时候会将这类定时删除)这样在执行update时,即使其在hash表中,也不会执行(因为deletion为true)。标识updateHashLocked,将在scheduler的update函数开始时置为true,然后在结尾置为false,其他时候不会被更改。update函数会在后面介绍,下面,继续看unschedule的其他方法。

void Scheduler::unscheduleAllForTarget(void *target)
{
    // explicit nullptr handling
    if (target == nullptr)
    {
        return;
    }

    // Custom Selectors
    tHashTimerEntry *element = nullptr;
    HASH_FIND_PTR(_hashForTimers, &target, element);

    if (element)
    {
        if (ccArrayContainsObject(element->timers, element->currentTimer)
            && (! element->currentTimerSalvaged))
        {
            element->currentTimer->retain();
            element->currentTimerSalvaged = true;
        }
        ccArrayRemoveAllObjects(element->timers);

        if (_currentTarget == element)
        {
            _currentTargetSalvaged = true;
        }
        else
        {
            removeHashElement(element);
        }
    }

    // update selector
    unscheduleUpdate(target);
}

该方法会移除target相关的所有定时,包括update类型的,包括Custom Selector类型的,和其他的一样,需要注意该标志位。

最后提一下unscheduleAllWithMinPriority,他会将custom 类型的定时全部移除,并将priority大于残烛的update类型定时移除。

2.3.3 定时器的更新update

void Scheduler::update(float dt)
{
    _updateHashLocked = true;

	//timeScale是什么意思呢,正常的速度是1.0,如果你想二倍速放就设置成2.0,如果你想慢慢放,就设置成0.5.
    if (_timeScale != 1.0f)
    {
        dt *= _timeScale;
    }

    //
    // Selector callbacks
    //

    // Iterate over all the Updates' selectors
    tListEntry *entry, *tmp;

	//首先处理update类型的定时,你可以发现想调用它的callback,必须满足markedForDeletion为false,从而证明我上面的说法。
    // updates with priority < 0
    DL_FOREACH_SAFE(_updatesNegList, entry, tmp)
    {
        if ((! entry->paused) && (! entry->markedForDeletion))
        {
            entry->callback(dt);
        }
    }

    // updates with priority == 0
    DL_FOREACH_SAFE(_updates0List, entry, tmp)
    {
        if ((! entry->paused) && (! entry->markedForDeletion))
        {
            entry->callback(dt);
        }
    }

    // updates with priority > 0
    DL_FOREACH_SAFE(_updatesPosList, entry, tmp)
    {
        if ((! entry->paused) && (! entry->markedForDeletion))
        {
            entry->callback(dt);
        }
    }

	//处理custom类型的定时
    // Iterate over all the custom selectors
    for (tHashTimerEntry *elt = _hashForTimers; elt != nullptr; )
    {
        _currentTarget = elt;
        _currentTargetSalvaged = false;
		//没有被暂停,则可以处理
        if (! _currentTarget->paused)
        {
            // The 'timers' array may change while inside this loop
			//循环内是当前target下的所有Timer
            for (elt->timerIndex = 0; elt->timerIndex < elt->timers->num; ++(elt->timerIndex))
            {
                elt->currentTimer = (Timer*)(elt->timers->arr[elt->timerIndex]);
                elt->currentTimerSalvaged = false;

                elt->currentTimer->update(dt);
				//如果currentTimer的update本身内部,在一定条件下unSchedule了本身,则会改变currentTimerSalvaged的标识信息,
				//所以要再次进行判断,这就是循环上面英文注释所述之意
                if (elt->currentTimerSalvaged)
                {
                    // The currentTimer told the remove itself. To prevent the timer from
                    // accidentally deallocating itself before finishing its step, we retained
                    // it. Now that step is done, it's safe to release it.
                    elt->currentTimer->release();
                }

                elt->currentTimer = nullptr;
            }
        }

        // elt, at this moment, is still valid
        // so it is safe to ask this here (issue #490)
        elt = (tHashTimerEntry *)elt->hh.next;

        // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
		//即使在大循环开始时_currentTargetSalvaged被设置为false,现在的值也可能因为上面该target的各种定时函数调用导致其为true
        if (_currentTargetSalvaged && _currentTarget->timers->num == 0)
        {
            removeHashElement(_currentTarget);
        }
    }

	//这些update类型的定时要被删除咯~~
    // delete all updates that are marked for deletion
    // updates with priority < 0
    DL_FOREACH_SAFE(_updatesNegList, entry, tmp)
    {
        if (entry->markedForDeletion)
        {
            this->removeUpdateFromHash(entry);
        }
    }

    // updates with priority == 0
    DL_FOREACH_SAFE(_updates0List, entry, tmp)
    {
        if (entry->markedForDeletion)
        {
            this->removeUpdateFromHash(entry);
        }
    }

    // updates with priority > 0
    DL_FOREACH_SAFE(_updatesPosList, entry, tmp)
    {
        if (entry->markedForDeletion)
        {
            this->removeUpdateFromHash(entry);
        }
    }

    _updateHashLocked = false;
    _currentTarget = nullptr;

}

到了最重要的函数了,当你把定时都放入了这些list后,定时器是如何按时调用的呢,答案就在update函数中。

update函数,最需要注意的点是什么?是在循环内部执行每个target的customer定时函数时候,需要注意很可能改变绑定在该Target下的Customer Timer的状态。所以在每次循环之后,都会判断这些状态位,如果被改变,需要做什么操作。在代码注释中,我已经说明。

2.3.4 状态查询与暂停恢复

bool isScheduled(const std::string& key, void *target);   &&  bool isScheduled(SEL_SCHEDULE selector, Ref *target);

可以查询customer类型的定时是否被scheduled。

void pauseTarget(void *target);   &&   void resumeTarget(void *target);

恢复和暂定target相关的所有定时。就是更改状态而已。。

2.3.5 3.x的新特性

自从3.x开始,进入了c++11的时代,与此同时,正式引入了多线程编程。本人对多线程了解不多,只能简单点出此函数,具体的用法,烦请各位看官谷歌或者微微一笑吧~

/** calls a function on the cocos2d thread. Useful when you need to call a cocos2d function from another thread.

This function is thread safe.

@since v3.0

*/

void performFunctionInCocosThread( const std::function<void()> &function);

3.小结

1.Scheduler与Timer的关系相当DataManager与Data的关系。

2.Scheduler的两种定时模式,一种是customer selector模式,一种是update 模式。

3.hash表用来存取对应的timer。

4.Scheduler的update函数调用了所有Timer的update。

Cocos2d-x 源码分析 : Scheduler(定时器) 源码分析

时间: 2024-10-03 09:54:56

Cocos2d-x 源码分析 : Scheduler(定时器) 源码分析的相关文章

Cocos2d-X3.0 刨根问底(六)----- 调度器Scheduler类源码分析

上一章,我们分析Node类的源码,在Node类里面耦合了一个 Scheduler 类的对象,这章我们就来剖析Cocos2d-x的调度器 Scheduler 类的源码,从源码中去了解它的实现与应用方法. 直入正题,我们打开CCScheduler.h文件看下里面都藏了些什么. 打开了CCScheduler.h 文件,还好,这个文件没有ccnode.h那么大有上午行,不然真的吐血了, 仅仅不到500行代码.这个文件里面一共有五个类的定义,老规矩,从加载的头文件开始阅读. #include <funct

cocos2d-x 源码分析 : control 源码分析 ( 控制类组件 controlButton)

源码版本来自3.1rc 转载请注明 cocos2d-x源码分析总目录 http://blog.csdn.net/u011225840/article/details/31743129 1.继承结构 control的设计整体感觉挺美的,在父类control定义了整个控制事件的基础以及管理,虽然其继承了Layer,但其本身和UI组件的实现并没有关联.在子类(controlButton,controlSwitch,controlStepper等中实现不同的UI组件).下面通过源码来分析control与

Netty源码学习——EventLoopGroup原理:NioEventLoopGroup分析

类结构图: 不了解Executor接口原理的可以查看concurrent包中的api介绍,这里只介绍Netty中EventExecutorGroup的主要功能! 从类的结构图中可以看到EventExecutorGroup是直接继承ScheduledExecutorService这个接口的,为了说明白Group的原理这里顺便提一下ScheduledExecutorService的用途! java.util.concurrent.ScheduledExecutorService An Executo

Tomcat源码分析之—具体启动流程分析

从Tomcat启动调用栈可知,Bootstrap类的main方法为整个Tomcat的入口,在init初始化Bootstrap类的时候为设置Catalina的工作路径也就是Catalina_HOME信息.Catalina.base信息,在initClassLoaders方法中初始化类加载器,然后通过反射初始化org.apache.catalina.startup.Catalina作为catalina守护进程: 一.load Bootstrap中load流程: 反射调用Catalina的load方法

【E2LSH源码分析】E2LSH源码综述及主要数据结构

上一小节,我们对p稳定分布LSH的基本原理进行了介绍(http://blog.csdn.net/jasonding1354/article/details/38237353),在接下来的博文中,我将以E2LSH开源代码为基础,对E2LSH的源码进行注解学习,从而为掌握LSH的基本原理以及未来对相似性搜索的扩展学习打下基础. 1.代码概况 E2LSH的核心代码可以分为3部分: LocalitySensitiveHashing.cpp--主要包含基于LSH的RNN(R-near neighbor)数

8、SpringMVC源码分析(3):分析ModelAndView的形成过程

首先,我们还是从DispatcherServlet.doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception方法开始,看看这个牛逼的ModelAndView是怎么开始的,又是怎么结束的: 1 protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Except

Solr4.8.0源码分析(5)之查询流程分析总述

Solr4.8.0源码分析(5)之查询流程分析总述 前面已经写到,solr查询是通过http发送命令,solr servlet接受并进行处理.所以solr的查询流程从SolrDispatchsFilter的dofilter开始.dofilter包含了对http的各个请求的操作.Solr的查询方式有很多,比如q,fq等,本章只关注select和q.页面下发的查询请求如下:http://localhost:8080/solr/test/select?q=code%3A%E8%BE%BD*+AND+l

fkdpbpSpriNgqurtz定时器源码配置

疰憨 fkdpbpSpriNgqurtz定时器源码配置

cocos2d-x 源码分析 之 CCTableView源码分析(附使用方法讨论)

cocos2d-x源码总目录 http://blog.csdn.net/u011225840/article/details/31743129 源码来自2.x,转载请注明 1.继承结构 首先来看下CCTableView的继承结构 从继承结构上看,CCTableView是一种CCScrollView,所以为了研究CCTableView的源码,清先去了解CCScrollView的源码http://blog.csdn.net/u011225840/article/details/30033501. 其

7、SpringMVC源码分析(2):分析HandlerAdapter.handle方法,了解handler方法的调用细节以及@ModelAttribute注解

从上一篇 SpringMVC源码分析(1) 中我们了解到在DispatcherServlet.doDispatch方法中会通过 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()) 这样的方式来执行request的handler方法. 先来分析一下ha.handle方法的调用过程:HandlerAdapter接口有一个抽象实现类AbstractHandlerMethodAdapter,在该抽象类中通过具体方法