/** * A sample TLM which uses the C++ API and implements a service thread. */ #include #include #include enum NICKEL_T { SILVER, NICKEL }; #define MAX_NICKELS 20 // 20 nickels is a dollar /** * Class Nickel * is an example class that gets created out of a pool of memory * and passes through a MSG_BOX to the service thread. You would * likely want to use a less silly name, like "Job". */ class Nickel { public: Nickel( NICKEL_T aType, int aTime ) : _type( aType ), _time( aTime ) { printf( "%s: Nickel(%d, %d)\n", TLM___->Name(), _type, _time ); } ~Nickel() { printf( "%s: ~Nickel(%d, %d)\n", TLM___->Name(), _type, _time ); } NICKEL_T _type; int _time; }; void ServiceThread( void* arg ); DECLARE_TLM( TLM, "Service", 0 ) THREADID threadId; // Fabricate an example event, such an event could come from a TLI instead. DT_INT16 Seconds( "S23" ); // Supplying an address instead of a tag // Allocate and Free from this pool, 20 blocks of unconstructed memory, // with each block being the size of Nickel. Alloc() will construct // an actual Nickel instance from one of the free blocks. MYPOOL nickel_pool; // This is the inter-thread queue. The main thread writes to this // while the service thread reads from it in a blocking way. The // main thread can terminate the service thread by simply writing // a nullptr_t into this. MSG_BOX nickel_q; void TLM::Init( req_init* req ) { printf( "%s: " __DATE__ " initializing...\n", Name() ); threadId = HlpThreadStart( ServiceThread, "ServiceThread", 0, PRIORITY_HIGH, // one higher than main thread 0 ); } void TLM::Scan() { static int last_time = -1; // This is any condition that generates another Nickel. Normally // the Nickel would be constructed from a TLI or any needed test. if( (Seconds % 5 == 0) && Seconds != last_time ) // a Nickel every 5 seconds { last_time = Seconds; // Alloc() is nice enough to call any matching Nickel constructor, // matched by the same parameters as any needed constructor. Nickel* n = nickel_pool.Alloc( SILVER, Seconds ); nickel_q.Write( n ); // At this point I do not own the Nickel, it's either in the // nickel_q or the service thread has aleady Read() it out of // there. The latter is likely because we created the // service thread at a higher priority than the main thread // on which this function runs. } } void TLM::DeInstall() { printf( "%s: deinstalling...\n", Name() ); // Terminate the ServiceThread(). nickel_q.Write( nullptr ); } void ServiceThread( void* arg ) { // This function is not a TLM member function, so get the // TLM pointer via TLM___-> printf( "%s: service thread running...\n", TLM___->Name() ); while( Nickel* n = nickel_q.Read() ) // break on nullptr_t { // I got Nickel and I own it. If I lose it this is // a realtime programming bug. I will not lose it. I will // read its contents, perform the implicated work, and then // be sure and Free() it. MYPOOL::Free() will call its // destructor and return the memory to the free pool. // WORK STEP: this could be a socket connection, or any thing // very complicated and time consuming, but it should block // when waiting. It must not hog the CPU in a busy loop. And it // should not block for more than a few seconds otherwise upon // SoftPLC termination this thread will not get around to the // nickel_q.Read() to see the nullptr_t in a timely fashion. // In this example our "work" is to simply printf() the Nickel. // You might want to call a work() function from here and pass it // the Nickel pointer. printf( "%s: Read() nickel type:%d, time:%d\n", TLM___->Name(), n->_type, n->_time ); // RECYCLE: be sure and do this thru every code path with obtained Nickels. nickel_pool.Free( n ); } printf( "%s: service thread ending.\n", TLM___->Name() ); }