/* -*- mode: c++; c-file-style: raknet; tab-always-indent: nil; -*- */ /** * @file * @brief RakPeer Implementation * * This file is part of RakNet Copyright 2003 Rakkarsoft LLC and Kevin Jenkins. * * Usage of Raknet is subject to the appropriate licence agreement. * "Shareware" Licensees with Rakkarsoft LLC are subject to the * shareware license found at * http://www.rakkarsoft.com/shareWareLicense.html which you agreed to * upon purchase of a "Shareware license" "Commercial" Licensees with * Rakkarsoft LLC are subject to the commercial license found at * http://www.rakkarsoft.com/sourceCodeLicense.html which you agreed * to upon purchase of a "Commercial license" * Custom license users are subject to the terms therein. * All other users are * subject to the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your * option) any later version. * * Refer to the appropriate license agreement for distribution, * modification, and warranty rights. */ #ifndef __SINGLE_PRODUCER_CONSUMER_H #define __SINGLE_PRODUCER_CONSUMER_H #include static const int MINIMUM_LIST_SIZE=8; namespace BasicDataStructures { template class SingleProducerConsumer { public: SingleProducerConsumer(); ~SingleProducerConsumer(); // WriteLock must be immediately followed by WriteUnlock. These two functions must be called in the same thread. SingleProducerConsumerType* WriteLock(void); // Cancelling locks cancels all locks back up to the data passed. So if you lock twice and cancel using the first lock, the second lock is ignored void CancelWriteLock(SingleProducerConsumerType* cancelToLocation); void WriteUnlock(void); // ReadLock must be immediately followed by ReadUnlock. These two functions must be called in the same thread. SingleProducerConsumerType* ReadLock(void); // Cancelling locks cancels all locks back up to the data passed. So if you lock twice and cancel using the first lock, the second lock is ignored void CancelReadLock(SingleProducerConsumerType* cancelToLocation); // Signals that we are done reading the data from ReadLock. void ReadUnlock(void); // Clear is not thread-safe and none of the lock or unlock functions should be called while it is running. void Clear(void); // An ESTIMATE of how many data elements are waiting to be read int Size(void) const; // Make sure that the pointer we done reading for the call to ReadUnlock is the right pointer. void AssertReadUnlockOrder(const SingleProducerConsumerType* data) const; private: struct DataPlusPtr { SingleProducerConsumerType object; // Ready to read is so we can use an equality boolean comparison, in case the writePointer var is trashed while context switching. volatile bool readyToRead; volatile DataPlusPtr *next; }; volatile DataPlusPtr *readAheadPointer; volatile DataPlusPtr *writeAheadPointer; volatile DataPlusPtr *readPointer; volatile DataPlusPtr *writePointer; unsigned readCount, writeCount; }; template SingleProducerConsumer::SingleProducerConsumer() { // Preallocate readPointer = new DataPlusPtr; writePointer=readPointer; readPointer->next = new DataPlusPtr; int listSize; #ifdef _DEBUG assert(MINIMUM_LIST_SIZE>=3); #endif for (listSize=2; listSize < MINIMUM_LIST_SIZE; listSize++) { readPointer=readPointer->next; readPointer->next = new DataPlusPtr; } readPointer->next->next=writePointer; // last to next = start readPointer=writePointer; readAheadPointer=readPointer; writeAheadPointer=writePointer; readCount=writeCount=0; } template SingleProducerConsumer::~SingleProducerConsumer() { volatile DataPlusPtr *next; readPointer=writeAheadPointer->next; while (readPointer!=writeAheadPointer) { next=readPointer->next; delete readPointer; readPointer=next; } delete readPointer; } template SingleProducerConsumerType* SingleProducerConsumer::WriteLock( void ) { if (writeAheadPointer->next==readPointer || writeAheadPointer->next->readyToRead==true) { volatile DataPlusPtr *originalNext=writeAheadPointer->next; writeAheadPointer->next=new DataPlusPtr; writeAheadPointer->next->next=originalNext; } volatile DataPlusPtr *last; last=writeAheadPointer; writeAheadPointer=writeAheadPointer->next; return (SingleProducerConsumerType*) last; } template void SingleProducerConsumer::CancelWriteLock( SingleProducerConsumerType* cancelToLocation ) { writeAheadPointer=(DataPlusPtr *)cancelToLocation; } template void SingleProducerConsumer::WriteUnlock( void ) { // DataPlusPtr *dataContainer = (DataPlusPtr *)structure; #ifdef _DEBUG assert(writePointer->next!=readPointer); assert(writePointer!=writeAheadPointer); #endif writeCount++; // User is done with the data, allow send by updating the write pointer writePointer->readyToRead=true; writePointer=writePointer->next; } template SingleProducerConsumerType* SingleProducerConsumer::ReadLock( void ) { if (readAheadPointer==writePointer || readAheadPointer->readyToRead==false) { return 0; } volatile DataPlusPtr *last; last=readAheadPointer; readAheadPointer=readAheadPointer->next; return (SingleProducerConsumerType*)last; } template void SingleProducerConsumer::CancelReadLock( SingleProducerConsumerType* cancelToLocation ) { #ifdef _DEBUG assert(readPointer!=writePointer); #endif readAheadPointer=(DataPlusPtr *)cancelToLocation; } template void SingleProducerConsumer::ReadUnlock( void ) { #ifdef _DEBUG assert(readAheadPointer!=readPointer); // If hits, then called ReadUnlock before ReadLock assert(readPointer!=writePointer); // If hits, then called ReadUnlock when Read returns 0 #endif readCount++; // Allow writes to this memory block readPointer->readyToRead=false; readPointer=readPointer->next; } template void SingleProducerConsumer::Clear( void ) { // Shrink the list down to MINIMUM_LIST_SIZE elements volatile DataPlusPtr *next; writePointer=readPointer->next; int listSize=1; next=readPointer->next; while (next!=readPointer) { listSize++; next=next->next; } while (listSize-- > MINIMUM_LIST_SIZE) { next=writePointer->next; delete writePointer; writePointer=next; } readPointer->next=writePointer; writePointer=readPointer; readAheadPointer=readPointer; writeAheadPointer=writePointer; readCount=writeCount=0; } template int SingleProducerConsumer::Size( void ) const { return writeCount-readCount; } template void SingleProducerConsumer::AssertReadUnlockOrder(const SingleProducerConsumerType* data) const { #ifdef _DEBUG assert(& readPointer->object==data); #endif } } #endif /// Test code for the SingleProducerConsumer class /* #include "SingleProducerConsumer.h" #include #include #include #include #include #include BasicDataStructures::SingleProducerConsumer spc; unsigned long readCount; unsigned __stdcall ProducerThread( LPVOID arguments ) { unsigned long producerCount; unsigned long *writeBlock; producerCount=0; while (readCount < 1000000000) { writeBlock=spc.WriteLock(); *writeBlock=producerCount; spc.WriteUnlock(); producerCount++; if ((producerCount%1000000)==0) { printf("WriteCount: %i. BufferSize=%i\n", producerCount, spc.Size()); } } printf("PRODUCER THREAD ENDED!\n"); return 0; } unsigned __stdcall ConsumerThread( LPVOID arguments ) { unsigned long *readBlock; while (readCount < 1000000000) { if ((readBlock=spc.ReadLock())!=0) { if (*readBlock!=readCount) { printf("Test failed! Expected %i got %i!\n", readCount, *readBlock); readCount = 1000000000; assert(0); } spc.ReadUnlock(); readCount++; if ((readCount%1000000)==0) { printf("ReadCount: %i. BufferSize=%i\n", readCount, spc.Size()); } } } printf("CONSUMER THREAD ENDED!\n"); return 0; } void main(void) { readCount=0; unsigned threadId1 = 0; unsigned threadId2 = 0; HANDLE thread1Handle, thread2Handle; thread1Handle=(HANDLE)_beginthreadex( NULL, 0, ProducerThread, 0, 0, &threadId1 ); thread2Handle=(HANDLE)_beginthreadex( NULL, 0, ConsumerThread, 0, 0, &threadId1 ); // SetThreadPriority(thread1Handle,THREAD_PRIORITY_BELOW_NORMAL); // SetThreadPriority(thread2Handle,THREAD_PRIORITY_ABOVE_NORMAL); while (readCount < 1000000000) { Sleep(1000); } char str[256]; printf("Press Enter to continue\n"); gets(str); } */