#pragma once #include #include #include "util/name.h" #include "util/Object.h" #include "rbx/intrusive_ptr_target.h" #include namespace RBX { // A simple class that is a globally unique identifier class Guid : boost::noncopyable { public: struct Scope { public: Scope() { setNull(); } void setNull() { name = &RBX::Name::getNullName(); } bool isNull() const { return *name == RBX::Name::getNullName(); } void set( const std::string& s ) { name = &RBX::Name::declare(s.c_str()); } void set( const char* s ) { name = &RBX::Name::declare(s); } const RBX::Name* getName() const { return name; } int compare(const Scope& other) const { return name->compare(*other.name); } bool operator ==(const Scope& other) const { return name->compare(*other.name) == 0; } bool operator <(const Scope& other) const { return name->compare(*other.name) < 0; } static const Scope& null(); private: static Scope nullScope; const RBX::Name* name; }; struct Data { Scope scope; int index; bool operator ==(const Data& other) const; bool operator <(const Data& other) const; // For debugging only. A string that is not guaranteed to be unique std::string readableString(int scopeLength = 4) const; }; private: Data data; public: Guid(); bool operator ==(const Guid& other) const { return data == other.data; } bool operator <(const Guid& other) const { return data < other.data; } // Compare 2 pairs of Guids. a0-a1 and b0-b1 are commutativity. Any item may be NULL static int compare(const Guid* a, const Guid* b); static int compare(const Guid* a0, const Guid* a1, const Guid* b0, const Guid* b1); // Used for serialization: void assign(Data data); void extract(Data &data) const { data = this->data; } void copyDataFrom(const Guid& other) { Data data; other.extract(data); this->assign(data); } // For debugging only. A string that is not guaranteed to be unique std::string readableString(int scopeLength = 4) const { return data.readableString(scopeLength); } static const RBX::Guid::Scope& getLocalScope(); static void generateRBXGUID(RBX::Guid::Scope& result); // Creates a string like this: RBXc200e36038c511ceae6208002b2b79ef static void generateRBXGUID(std::string& result); // Creates a string like this: {c200e360-38c5-11ce-ae62-08002b2b79ef} static void generateStandardGUID(std::string& result); }; inline size_t hash_value(const Guid::Data& data) { size_t result = 0; boost::hash_combine(result, data.scope.getName()); boost::hash_combine(result, data.index); return result; } // A base class for objects that contain a Guid and that want lookup-by-Guid template class RBXBaseClass GuidItem { public: // A Registry maintains lookup information for Guids. // Each instance can belong to only one Registry. // Usually a Registry is associated with a DataModel class Registry : public rbx::quick_intrusive_ptr_target { friend class GuidItem; typedef boost::unordered_map > Map; Map map; RBX::mutex mutex; Registry() {} public: static boost::intrusive_ptr create() { return boost::intrusive_ptr(new Registry()); } ~Registry() { RBXASSERT(map.size() ==0); } // Returns true for empty Guid data, false for unregistered Guid data bool lookupByGuid(const Guid::Data& data, shared_ptr &result) { if(data.scope.isNull()) { result.reset(); return true; } RBX::mutex::scoped_lock lock(mutex); typename Map::const_iterator iter = GuidItem::Registry::map.find(data); if (iter!=map.end()) { result = iter->second.lock(); } else { result.reset(); } return !!result; } shared_ptr getByGuid(const Guid::Data& data) { shared_ptr result; lookupByGuid(data, result); return result; } // Ensure that this Guid is in the registry (thread-safe) void registerGuid(const T* item) { reg(item); } // Assigns a new guid to item. (not thread-safe) void assignGuid(T* item, const Guid::Data& guidData) { if (item->registry) item->registry->unregister(item); item->guid.assign(guidData); reg(item); item->onGuidChanged(); } void tryUnregister(GuidItem* item) { // In ClientReplicator::streamOutInstance(), we are traverse all the part's descendants and unregister them // It is possible some descendants have been already GC'd and unregistered earlier from the same GC loop in GCJob::gcRegion() // Here we simply skip it if the item has already been unregistered. if (item->registry) { unregister(item); } } void unregister(GuidItem* item) { RBXASSERT(item->registry.get()==this); Guid::Data data; item->guid.extract(data); { RBX::mutex::scoped_lock lock(mutex); int num = map.erase(data); RBXASSERT(num == 1); } item->registry.reset(); } private: void reg(const T* item) { if (!item->registry) { Guid::Data data; item->guid.extract(data); RBX::mutex::scoped_lock lock(mutex); if (!item->registry) // thread-safe check { map[data] = weak_from(const_cast(item)); item->registry = this; } } else RBXASSERT(item->registry.get()==this); } }; friend class Registry; private: mutable boost::intrusive_ptr registry; Guid guid; public: GuidItem() { } ~GuidItem() { if (registry) registry->unregister(this); } const Guid& getGuid() const { return guid; } }; };