001:
002:
003:
004:
005:
006:
007:
008:
009:
010:
011:
012:
013:
014:
015:
016:
017:
018:
019:
020:
021:
022:
023:
024:
025:
026:
027:
028:
029:
030:
031:
032:
033:
034:
035:
036:
037:
038:
039:
040:
041:
042:
043:
044:
045:
046:
047:
048:
049:
050:
051:
052:
053:
054:
055:
056:
057:
058:
059:
060:
061:
062:
063:
064:
065:
066:
067:
068:
069:
070:
071:
072:
073:
074:
075:
076:
077:
078:
079:
080:
081:
082:
083:
084:
085:
086:
087:
088:
089:
090:
091:
092:
093:
094:
095:
096:
097:
098:
099:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:
250:
251:
252:
253:
254:
255:
256:
257:
258:
259:
260:
261:
262:
263:
264:
265:
266:
267:
268:
269:
270:
271:
272:
273:
274:
275:
276:
277:
278:
279:
280:
281:
282:
283:
284:
285:
286:
287:
288:
289:
290:
291:
292:
293:
294:
295:
296:
297:
298:
299:
300:
301:
302:
303:
304:
305:
306:
307:
308:
309:
310:
311:
312:
313:
314:
315:
316:
317:
318:
319:
320:
321:
322:
323:
324:
325:
326:
327:
328:
329:
330:
331:
332:
333:
334:
335:
336:
337:
338:
339:
340:
341:
342:
343:
344:
345:
346:
347:
348:
349:
350:
351:
352:
353:
354:
355:
356:
357:
358:
359:
360:
361:
362:
363:
364:
365:
366:
367:
368:
369:
370:
371:
372:
373:
374:
375:
376:
377:
378:
379:
380:
381:
382:
383:
384:
385:
386:
387:
388:
389:
390:
391:
392:
393:
394:
395:
396:
397:
398:
399:
400:
401:
402:
403:
404:
405:
406:
407:
408:
409:
410:
411:
412:
413:
414:
415:
416:
417:
418:
419:
420:
421:
422:
423:
424:
425:
426:
427:
428:
429:
430:
431:
432:
433:
434:
435:
436:
437:
438:
439:
440:
441:
442:
443:
444:
445:
446:
447:
448:
449:
450:
451:
452:
453:
454:
455:
456:
457:
458:
459:
460:
461:
462:
463:
464:
465:
466:
467:
468:
469:
470:
471:
472:
473:
474:
475:
476:
477:
478:
479:
480:
481:
482:
483:
484:
485:
486:
487:
488:
489:
490:
491:
492:
493:
494:
495:
496:
497:
498:
499:
500:
501:
502:
503:
504:
505:
506:
507:
508:
509:
510:
511:
512:
513:
514:
515:
516:
517:
518:
519:
520:
521:
522:
523:
524:
525:
526:
527:
528:
529:
530:
531:
532:
533:
534:
535:
536:
537:
538:
539:
540:
541:
542:
543:
544:
545:
546:
547:
548:
549:
550:
551:
552:
553:
554:
555:
556:
557:
558:
559:
560:
561:
562:
563:
564:
565:
566:
567:
568:
569:
570:
571:
572:
573:
574:
575:
576:
577:
578:
579:
580:
581:
582:
583:
584:
585:
586:
587:
588:
589:
590:
591:
592:
593:
594:
595:
596:
597:
598:
599:
600:
601:
602:
603:
604:
605:
606:
607:
608:
609:
610:
611:
612:
613:
614:
615:
616:
617:
618:
619:
620:
621:
622:
623:
624:
625:
626:
627:
628:
629:
630:
631:
632:
633:
634:
635:
636:
637:
638:
639:
640:
641:
642:
643:
644:
645:
646:
647:
648:
649:
650:
651:
652:
653:
654:
655:
656:
657:
658:
659:
660:
661:
662:
663:
664:
665:
666:
667:
668:
669:
670:
671:
672:
673:
674:
675:
676:
677:
678:
679:
680:
681:
682:
683:
684:
685:
686:
687:
688:
689:
690:
691:
692:
693:
694:
695:
696:
697:
698:
699:
700:
701:
702:
703:
704:
705:
706:
707:
708:
709:
710:
/*
* This file is part of the Omni C++ framework
*
* Copyright (c) 2016, Zeriph Enterprises, LLC
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Zeriph, Zeriph Enterprises, LLC, nor the names
* of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY ZERIPH AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL ZERIPH AND CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <omni/chrono/timer.hpp>
#include <omni/sync/basic_thread.hpp>
#if !defined(OMNI_OS_WIN)
    #include <errno.h> // errno
#endif
#include <omni/constants.hpp>
#include <omni/exception.hpp>

#if defined(OMNI_SAFE_TIMER)
    #include <omni/sync/scoped_lock.hpp>
    #define OMNI_SAFE_TMQMTX_FW m_mtx(), m_qmtx(),
    #define OMNI_TMALOCK_FW omni::sync::scoped_lock<omni::sync::basic_lock> mlock(&this->m_mtx);
    #define OMNI_TMLOCK_FW this->m_mtx.lock();
    #define OMNI_TMUNLOCK_FW this->m_mtx.unlock();
    #define OMNI_TMQALOCK_FW omni::sync::scoped_lock<omni::sync::basic_lock> qlock(&this->m_qmtx);
    #define OMNI_TMQLOCK_FW this->m_qmtx.lock();
    #define OMNI_TMQUNLOCK_FW this->m_qmtx.unlock();
#else
    #define OMNI_SAFE_TMQMTX_FW
    #define OMNI_TMALOCK_FW
    #define OMNI_TMLOCK_FW
    #define OMNI_TMUNLOCK_FW
    #define OMNI_TMQALOCK_FW
    #define OMNI_TMQLOCK_FW
    #define OMNI_TMQUNLOCK_FW
#endif
/* these defines are used (instead of just using the defaults)
to aid in compiler times and memory usage (the compiler doesn't
have to rearrange the variables on construction to match the
structure, as well it doesn't have to do extra padding) */

#if defined(OMNI_DISPOSE_EVENT)
    #define OMNI_TMRDE_FW disposing(),
    #define OMNI_TMRDE_CP_FW(cp) disposing(cp.disposing),
#else
    #define OMNI_TMRDE_FW
    #define OMNI_TMRDE_CP_FW(cp)
#endif
#if defined(OMNI_OBJECT_NAME)
    #define OMNI_TMRNM_FW name(OMNI_STRW("omni::timer")),
    #define OMNI_TMRNM_CP_FW(cp) name(cp.name),
#else
    #define OMNI_TMRNM_FW
    #define OMNI_TMRNM_CP_FW(cp)
#endif
#if defined(OMNI_TYPE_INFO)
    #define OMNI_TMRTI_FW m_type(),
#else
    #define OMNI_TMRTI_FW
#endif

typedef struct omni_tick_event_args {
    omni::timer* timer;
    omni::timer::tick_event_args* args;
} omni_tick_event_args;

static void omni_tick_event_sync_noblock(void* param)
{
    omni_tick_event_args* t = static_cast<omni_tick_event_args*>(param);
    if (t && t->timer && t->args) {
        if (t->timer->tick) {
            t->timer->tick(*t->args);
        }
    }
}

omni::timer::timer() :
    state_object(),
    tick(),
    OMNI_TMRDE_FW
    OMNI_TMRNM_FW
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(100),
    m_tktype(omni::timer::tick_event_type::SYNC_NOBLOCK),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    OMNI_DV5_FW("created with interval of ", this->m_int);
}

omni::timer::timer(omni::timer::tick_event_type_t tetype) :
    state_object(),
    tick(),
    OMNI_TMRDE_FW
    OMNI_TMRNM_FW
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(100),
    m_tktype(tetype),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    OMNI_DV5_FW("created with interval of ", this->m_int);
}

omni::timer::timer(omni::timer::tick_event_type_t tetype, std::size_t ival) :
    state_object(),
    tick(),
    OMNI_TMRDE_FW
    OMNI_TMRNM_FW
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(ival),
    m_tktype(tetype),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    OMNI_DV5_FW("created with interval of ", this->m_int);
}

omni::timer::timer(omni::timer::tick_event_type_t tetype, std::size_t ival, omni::generic_ptr so) :
    state_object(so),
    tick(),
    OMNI_TMRDE_FW
    OMNI_TMRNM_FW
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(ival),
    m_tktype(tetype),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    OMNI_DV5_FW("created with event and interval of ", this->m_int);
}

omni::timer::timer(omni::timer::tick_event_type_t tetype, const omni::timer::tick_delegate &fn, std::size_t ival) :
    state_object(),
    tick(),
    OMNI_TMRDE_FW
    OMNI_TMRNM_FW
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(ival),
    m_tktype(tetype),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    this->tick += fn;
    OMNI_DV5_FW("created with event and interval of ", this->m_int);
}

omni::timer::timer(omni::timer::tick_event_type_t tetype, const omni::timer::tick_delegate &fn, std::size_t ival, omni::generic_ptr so) :
    state_object(so),
    tick(),
    OMNI_TMRDE_FW
    OMNI_TMRNM_FW
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(ival),
    m_tktype(tetype),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    this->tick += fn;
    OMNI_DV5_FW("created with event and interval of ", this->m_int);
}

omni::timer::timer(omni::timer::tick_event_type_t tetype, const omni::timer::tick_delegate &fn, std::size_t ival, std::size_t delay) :
    state_object(),
    tick(),
    OMNI_TMRDE_FW
    OMNI_TMRNM_FW
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(ival),
    m_tktype(tetype),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    this->tick += fn;
    OMNI_DV5_FW("created with event and interval of ", this->m_int);
    this->start(delay);
}

omni::timer::timer(omni::timer::tick_event_type_t tetype, const omni::timer::tick_delegate &fn, std::size_t ival, std::size_t delay, omni::generic_ptr so) :
    state_object(so),
    tick(),
    OMNI_TMRDE_FW
    OMNI_TMRNM_FW
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(ival),
    m_tktype(tetype),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    this->tick += fn;
    OMNI_DV5_FW("created with event and interval of ", this->m_int);
    this->start(delay);
}

omni::timer::timer(std::size_t ival) :
    state_object(),
    tick(),
    OMNI_TMRDE_FW
    OMNI_TMRNM_FW
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(ival),
    m_tktype(omni::timer::tick_event_type::SYNC_NOBLOCK),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    OMNI_DV5_FW("created with interval of ", this->m_int);
}

omni::timer::timer(std::size_t ival, omni::generic_ptr so) :
    state_object(so),
    tick(),
    OMNI_TMRDE_FW
    OMNI_TMRNM_FW
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(ival),
    m_tktype(omni::timer::tick_event_type::SYNC_NOBLOCK),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    OMNI_DV5_FW("created with event and interval of ", this->m_int);
}

omni::timer::timer(const omni::timer::tick_delegate &fn, std::size_t ival) :
    state_object(),
    tick(),
    OMNI_TMRDE_FW
    OMNI_TMRNM_FW
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(ival),
    m_tktype(omni::timer::tick_event_type::SYNC_NOBLOCK),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    this->tick += fn;
    OMNI_DV5_FW("created with event and interval of ", this->m_int);
}

omni::timer::timer(const omni::timer::tick_delegate &fn, std::size_t ival, omni::generic_ptr so) :
    state_object(so),
    tick(),
    OMNI_TMRDE_FW
    OMNI_TMRNM_FW
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(ival),
    m_tktype(omni::timer::tick_event_type::SYNC_NOBLOCK),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    this->tick += fn;
    OMNI_DV5_FW("created with event and interval of ", this->m_int);
}

omni::timer::timer(const omni::timer::tick_delegate &fn, std::size_t ival, std::size_t delay) :
    state_object(),
    tick(),
    OMNI_TMRDE_FW
    OMNI_TMRNM_FW
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(ival),
    m_tktype(omni::timer::tick_event_type::SYNC_NOBLOCK),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    this->tick += fn;
    OMNI_DV5_FW("created with event and interval of ", this->m_int);
    this->start(delay);
}

omni::timer::timer(const omni::timer::tick_delegate &fn, std::size_t ival, std::size_t delay, omni::generic_ptr so) :
    state_object(so),
    tick(),
    OMNI_TMRDE_FW
    OMNI_TMRNM_FW
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(ival),
    m_tktype(omni::timer::tick_event_type::SYNC_NOBLOCK),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    this->tick += fn;
    OMNI_DV5_FW("created with event and interval of ", this->m_int);
    this->start(delay);
}

omni::timer::timer(const omni::timer& cp) :
    state_object(),
    tick(),
    OMNI_TMRDE_CP_FW(cp)
    OMNI_TMRNM_CP_FW(cp)
    OMNI_TMRTI_FW
    OMNI_SAFE_TMQMTX_FW
    m_que(),
    m_thread(0),
    m_qthread(0),
    m_int(100),
    m_tktype(omni::timer::tick_event_type::SYNC_NOBLOCK),
    m_auto(true),
    m_isrun(false),
    m_stopreq(false)
{
    #if defined(OMNI_SAFE_TIMER)
        cp.m_mtx.lock();
        cp.m_qmtx.lock();
    #endif
    this->state_object = cp.state_object;
    this->tick = cp.tick;
    this->m_int = cp.m_int;
    this->m_tktype = cp.m_tktype;
    this->m_que = cp.m_que;
    this->m_auto = cp.m_auto;
    bool isrun = cp.m_isrun;
    bool stopreq = cp.m_stopreq;
    #if defined(OMNI_SAFE_TIMER)
        cp.m_qmtx.unlock();
        cp.m_mtx.unlock();
    #endif
    if (isrun && !stopreq) { this->start(); }
    OMNI_D5_FW("copied");
}

omni::timer::~timer()
{
    OMNI_DTOR_FW
    this->stop();
    OMNI_D5_FW("destroyed");
}

bool omni::timer::auto_reset() const
{
    OMNI_TMALOCK_FW
    return this->m_auto;
}

std::size_t omni::timer::interval() const
{
    OMNI_TMALOCK_FW
    return this->m_int;
}

bool omni::timer::is_running() const
{
    OMNI_TMALOCK_FW
    return this->m_isrun;
}

void omni::timer::reset()
{
    this->stop();
    this->start();
}

void omni::timer::set_auto_reset(bool autoreset)
{
    OMNI_TMALOCK_FW
    this->m_auto = autoreset;
}

void omni::timer::set_interval(std::size_t ival)
{
    OMNI_TMALOCK_FW
    this->m_int = ival;
}

void omni::timer::start()
{
    this->start(0);
}

void omni::timer::start(std::size_t delay)
{
    if (!this->is_running()) {
        { // error check scope (for auto mutex)
            OMNI_TMALOCK_FW
            if (!this->tick) {
                OMNI_ERR_RET_FW(omni::consts::err::INVALID_DELEGATE_FUNC_PTR, omni::invalid_delegate())
            }
            if (this->m_int == 0) {
                OMNI_ERRV_RET_FW("Start error: ", omni::consts::err::INDEX_OOR, omni::index_out_of_range())
            }
            this->m_stopreq = false;
        }
        void *d = static_cast<void*>(&delay);
        // thread start_type == now
        this->m_thread = new omni::sync::basic_thread(omni::sync::parameterized_thread_start::bind< omni::timer, &omni::timer::_run >(*this), d);
    }
    #if defined(OMNI_DBG_L2)
    else { OMNI_D2_FW("The timer is already running"); }
    #endif
    
}

void omni::timer::stop()
{
    if (this->is_running()) {
        OMNI_TMLOCK_FW
        this->m_stopreq = true;
        OMNI_TMUNLOCK_FW
        if (this->m_tktype == omni::timer::tick_event_type::SYNC_QUEUE) {
            this->m_thread->join();
        } else {
            this->m_thread->join(500);
            if (this->is_running()) { // still running?
                OMNI_D2_FW("timer still running after stop request, killing thread...");
                this->m_thread->kill();
            }
        }
        if (this->m_qthread != 0) {
            // this shouldn't happen, but here as a precaution the
            // main thread gets hosed we can at least clean up
            delete this->m_qthread;
            this->m_qthread = 0;
        }
        delete this->m_thread;
        this->m_thread = 0;
    }
    #if defined(OMNI_DBG_L2)
    else { OMNI_D2_FW("the timer is not running"); }
    #endif
}

omni::timer &omni::timer::operator=(const omni::timer &other)
{
    if (this != &other) {
        this->stop();
        OMNI_ASSIGN_FW(other)
        #if defined(OMNI_SAFE_TIMER)
            this->m_mtx.lock();
            this->m_qmtx.lock();
            other.m_mtx.lock();
            other.m_qmtx.lock();
        #endif
        this->state_object = other.state_object;
        this->tick = other.tick;
        this->m_int = other.m_int;
        this->m_tktype = other.m_tktype;
        this->m_que = other.m_que;
        this->m_auto = other.m_auto;
        bool isrun = other.m_isrun;
        bool stopreq = other.m_stopreq;
        this->m_isrun = false;
        this->m_stopreq = false;
        #if defined(OMNI_SAFE_TIMER)
            other.m_qmtx.unlock();
            other.m_mtx.unlock();
            this->m_qmtx.unlock();
            this->m_mtx.unlock();
        #endif
        if (isrun && !stopreq) { this->start(); }
    }
    return *this;
}

bool omni::timer::operator==(const omni::timer &o) const
{
    if (this == &o) { return true; }
    #if defined(OMNI_SAFE_TIMER)
        omni::sync::scoped_lock<omni::sync::basic_lock> mlock(&this->m_mtx);
        omni::sync::scoped_lock<omni::sync::basic_lock> qlock(&this->m_qmtx);
        omni::sync::scoped_lock<omni::sync::basic_lock> omlock(&o.m_mtx);
        omni::sync::scoped_lock<omni::sync::basic_lock> oqlock(&o.m_qmtx);
    #endif
    return (this->state_object == o.state_object &&
            this->tick == o.tick &&
            this->m_que == o.m_que &&
            (((this->m_thread != 0) && (o.m_thread != 0)) ?
            (*this->m_thread == *o.m_thread)
            : (this->m_thread == o.m_thread)) &&
            (((this->m_qthread != 0) && (o.m_qthread != 0)) ?
            (*this->m_qthread == *o.m_qthread)
            : (this->m_qthread == o.m_qthread)) &&
            this->m_tktype == o.m_tktype &&
            this->m_int == o.m_int &&
            this->m_auto == o.m_auto &&
            this->m_isrun == o.m_isrun &&
            this->m_stopreq == o.m_stopreq)
            OMNI_EQUAL_FW(o);
}

bool omni::timer::operator!=(const omni::timer &o) const
{
    return !(*this == o);
}

///////// start private methods /////////

void omni::timer::_run(void* dly) // thread run
{
    OMNI_TMLOCK_FW
    this->m_isrun = true;
    OMNI_TMUNLOCK_FW
    bool is_qs = (this->m_tktype == omni::timer::tick_event_type::SYNC_QUEUE || this->m_tktype == omni::timer::tick_event_type::SYNC_SKIP);
    if (is_qs) {
        // only start this when the queue has something in it
        this->m_qthread = new omni::sync::basic_thread(
            omni::sync::thread_start::bind< omni::timer, &omni::timer::_run_queued >(*this),
            omni::sync::thread_start_type::USER);
    }
    OMNI_SLEEP_INIT();
    std::size_t delay = *static_cast<std::size_t*>(dly);
    omni::chrono::tick_t st = omni::chrono::monotonic_tick();
    if (delay > 0) {
        OMNI_D5_FW("waiting to start, delay " << delay << "ms");
        while (!this->_stopreq() && (omni::chrono::elapsed_ms(st) < delay)) {
            OMNI_SLEEP1();
        }
    }
    OMNI_D5_FW("start timer");
    do {
        if (this->_stopreq()) { break; }
        st = omni::chrono::monotonic_tick();
        #if defined(OMNI_SAFE_TIMER)
            while ((omni::chrono::elapsed_ms(st) < this->interval())) {
                    //omni::yield_thread();
                    OMNI_SLEEP1();
                    if (this->_stopreq()) { break; }
            }
        #else
            while ((omni::chrono::elapsed_ms(st) < this->m_int)) {
                //omni::yield_thread();
                OMNI_SLEEP1();
                if (this->m_stopreq) { break; }
            }
        #endif
        if (this->_stopreq()) { break; }
        omni::timer::tick_event_args tea(omni::chrono::monotonic_tick(), this->state_object);
        switch (this->m_tktype) {
            case omni::timer::tick_event_type::SYNC_NOBLOCK: {
                    // don't block the timer tick thread, spin off a new thread to do the 'tick'
                    omni_tick_event_args ota;
                    ota.timer = this;
                    ota.args = &tea;
                    void* p = static_cast<void*>(&ota);
                    omni::sync::basic_thread syt(&omni_tick_event_sync_noblock, p);
                } break;
            case omni::timer::tick_event_type::SYNC_BLOCK:
                // block the tick thread until the tick event finishes
                if (this->tick) { this->tick(tea); }
                break;
            case omni::timer::tick_event_type::SYNC_QUEUE: {
                    // queue up the event and spawn a thread to do the queue if necessary
                    OMNI_TMQLOCK_FW
                    this->_add_queue(tea);
                    OMNI_TMQUNLOCK_FW
                } break;
            case omni::timer::tick_event_type::SYNC_SKIP: {
                    // skip if !queue.empy(), otherwise act like SYNC_QUEUE
                    OMNI_TMQLOCK_FW
                    if (this->m_que.empty()) {
                        this->_add_queue(tea);
                    }
                    OMNI_TMQUNLOCK_FW
                } break;
            // pedant: .. shouldn't get here since "we" control this; i.e. user can't change m_tktype
            default: break;
        }
    } while (this->auto_reset());
    if (is_qs) {
        OMNI_TMQLOCK_FW
        if (!this->m_que.empty()) { this->m_que.clear(); }
        OMNI_TMQUNLOCK_FW
        if (this->m_tktype == omni::timer::tick_event_type::SYNC_QUEUE) {
            this->m_qthread->join();
        } else {
            this->m_qthread->join(500);
            if (this->m_qthread->is_alive()) { // still running?
                OMNI_D2_FW("timer still running after stop request, killing thread...");
                this->m_qthread->kill();
            }
        }
        delete this->m_qthread;
        this->m_qthread = 0;
    }
    OMNI_TMLOCK_FW
    this->m_isrun = false;
    OMNI_TMUNLOCK_FW
}

void omni::timer::_run_queued()
{
    while (!this->_stopreq()) {
        OMNI_TMQLOCK_FW
        if (this->m_que.empty()) {
            OMNI_TMQUNLOCK_FW
            return;
        }
        omni::timer::tick_event_args arg(this->m_que.front());
        OMNI_TMQUNLOCK_FW
        if (this->tick) { this->tick(arg); }
        if (this->_stopreq()) { return; }
        OMNI_TMQLOCK_FW
        if (this->m_que.empty()) {
            OMNI_TMQUNLOCK_FW
            return;
        }
        // don't pop until we're done
        this->m_que.pop_front();
        OMNI_TMQUNLOCK_FW
    }
}

void omni::timer::_add_queue(const omni::timer::tick_event_args& tea)
{
    if (this->_stopreq()) { return; }
    this->m_que.push_back(tea);
    if (this->m_qthread && !this->m_qthread->is_alive()) {
        // restart the basic_thread (detach/start)
        this->m_qthread->detach();
        this->m_qthread->start();
    }
}

bool omni::timer::_stopreq() const
{
    OMNI_TMALOCK_FW
    return this->m_stopreq;
}

bool omni::timer::_queue_empty() const
{
    OMNI_TMQALOCK_FW
    return this->m_que.empty();
}