// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #ifndef NL_CHANGE_TRACKER_BASE_H #define NL_CHANGE_TRACKER_BASE_H #include "nel/misc/types_nl.h" #include "base_types.h" #include // TEMP //#ifdef NL_OS_WINDOWS #define USE_FAST_MUTEX //#endif /* * Set this define for stats about the property changes (slower) * Important note: all the user services and the mirror service should have the same value! */ #define COUNT_MIRROR_PROP_CHANGES /** * Header of a tracker */ struct TChangeTrackerHeader { /** First changed item (useful to read the changed from the beginning). * When there is no change to read, First equals LAST_CHANGED. */ volatile TDataSetIndex First; /** Last changed item (useful to link the last changed to a newly changed item). * When there is no change yet, Last equals INVALID_DATASET_ROW. */ volatile TDataSetIndex Last; #ifdef USE_FAST_MUTEX /// Fast mutex (TODO: use multi-processor version) volatile NLMISC::CFastMutex FastMutex; #endif /* * Number of values set (used in COUNT_MIRROR_CHANGES mode only, always allocated for mode interoperability) * Currently, not implemented. */ //volatile sint32 NbValuesSet; /** * Number of changes really recorded (used in COUNT_MIRROR_CHANGES mode only, always allocated for mode interoperability). * NbValuesSet-NbDistinctChanges is the number of skipped changes. */ volatile sint32 NbDistinctChanges; }; const uint16 LOCAL_TRACKER_SERVICE_ID = (uint16)~0; /** * Item in a tracker */ struct TChangeTrackerItem { /** * If the item state is 'unchanged', NextChanged is INVALID_DATASET_ROW. * If it is 'changed', NextChanged is either the index of the next changed * item or the value LAST_CHANGED. */ TDataSetIndex NextChanged; }; /** * Base class for change tracker * * \author Olivier Cado * \author Nevrax France * \date 2002 */ class CChangeTrackerBase { public: /// Constructor CChangeTrackerBase() : _SMId(-1), _MutId(-1), _Header(NULL), _Array(NULL) {} /// Assignment operator CChangeTrackerBase& operator = ( const CChangeTrackerBase& src ) { if ( &src == this ) return *this; _SMId = src._SMId; #ifndef USE_FAST_MUTEX _TrackerMutex = src._TrackerMutex; // contains only the mutex handle #endif _MutId = src._MutId; _Header = src._Header; // leave the pointed data where they are (in shared memory) _Array = src._Array; // same return *this; } /// Return the shared memory id const sint32& smid() const { return _SMId; } /// Return true if the tracker header and item array are already allocated bool isAllocated() const { return _Array != NULL; } /// Return the pointer to the header (root of the shared memory segment) TChangeTrackerHeader *header() { return _Header; } /// Record a change (push) (assumes isAllocated()) void recordChange( TDataSetIndex entityIndex ); /// Remove a change if found in the tracker (slow) (assumes isAllocated()) void cancelChange( TDataSetIndex entityIndex ); /// Get the entity index of the first changed (assumes isAllocated()). Returns LAST_CHANGED if there is no change. uint32 getFirstChanged() const { /*nlinfo( "Array = %p, First = %d, _Array[First].NextChanged = %d, _Array[0].NextChanged = %d", _Array, _Header->First, _Array[_Header->First].NextChanged, _Array[0].NextChanged );*/ return _Header->First; } /// Pop the first change out of the tracker. Do not call if getFirstChanged() returned LAST_CHANGED. void popFirstChanged() { // Protect consistency of popFirstChanged() in parallel with recordChange() // (there can't be two parallels calls to popFirstChanged() trackerMutex().enter(); #ifdef NL_DEBUG nlassert( _Header->First != LAST_CHANGED ); #endif TChangeTrackerItem *queueFront = &(_Array[_Header->First]); _Header->First = queueFront->NextChanged; queueFront->NextChanged = INVALID_DATASET_INDEX; if ( _Header->First == LAST_CHANGED ) { _Header->Last = INVALID_DATASET_INDEX; } #ifdef COUNT_MIRROR_PROP_CHANGES --_Header->NbDistinctChanges; #endif trackerMutex().leave(); } /// Return the number of changes (assumes isAllocated()) (slow) sint32 nbChanges() const; #ifdef USE_FAST_MUTEX /// Return the mutex volatile NLMISC::CFastMutex& trackerMutex() { return _Header->FastMutex; } #else /// Return the mutex NLMISC::CSharedMutex& trackerMutex() { return _TrackerMutex; } #endif /// Return the mutex id const sint32& mutid() const { return _MutId; } /// Create the mutex (either create or use existing) bool createMutex( sint32 mutid, bool createNew ); /// Display debug info (1 line) void displayTrackerInfo( const char *headerStr="", const char *footerStrNotAllocd="", NLMISC::CLog *log=NLMISC::InfoLog ) const; /// Serial ids void serial( NLMISC::IStream& s ) { s.serial( _SMId ); s.serial( _MutId ); } protected: /// Get the entity index of the next changed (assumes isAllocated() and entityIndex is valid). Returns LAST_CHANGED if there is no more change. TDataSetRow getNextChanged( const TDataSetRow& entityIndex ) const { /*nlinfo( "_Array[%d].NextChanged = %d", entityIndex, _Array[entityIndex].NextChanged );*/ return TDataSetRow(_Array[entityIndex.getIndex()].NextChanged); } /// Shared memory numeric id sint32 _SMId; #ifndef USE_FAST_MUTEX /// Mutex NLMISC::CSharedMutex _TrackerMutex; #endif /// Mutex id sint32 _MutId; /// Pointer to the handling variables of the tracker (may be pointing to shared memory) TChangeTrackerHeader *_Header; /// Pointer to the array of entities, indexed by TDataSetRow (may be pointing to shared memory) TChangeTrackerItem *_Array; }; #endif // NL_CHANGE_TRACKER_BASE_H /* End of change_tracker_base.h */