Newer
Older
Import / applications / HighwayDash / ports / Framework / RefCounting.cpp
//  BlockyFroggy
//  Copyright © 2017 John Ryland.
//  All rights reserved.
#include "RefCounting.h"
#include <atomic>


class ReferenceCount
{
public:
  operator size_t() const { return (m_count == nullptr) ? 1 : (*m_count).load(); }

  void operator++()
  {
    if (m_count == nullptr)
    {
      m_count = new std::atomic<size_t>;
      *m_count = 2;
    }
    else
    {
      (*m_count)++;
    }
  }
  
  // returns true if need to delete the object
  bool operator--()
  {
    if (operator size_t() == 1) // We are the sole owner left
    {
      if (m_count)
        delete m_count;
      m_count = nullptr;
      return true;
    }
    else
    {
      (*m_count)--;
    }
    return false;
  }
  
  void reset()
  {
    m_count = nullptr;
  }

private:
  std::atomic<size_t>* m_count = nullptr; // a nullptr refCount is semantically equivolent to a refCount of one
};


template <typename T>
class ReferenceCountedObject
{
public:
  // Default construct an object
  ReferenceCountedObject()
  {
  }
  
  // Copy constructor
  explicit ReferenceCountedObject(const ReferenceCountedObject& a_other)
  {
    m_data = a_other.m_data;
    a_other.addRef();
    m_refCount = a_other.m_refCount;
  }
  
  // Assignment operator
  ReferenceCountedObject& operator=(const ReferenceCountedObject& a_other)
  {
    if (&a_other != this) // Self assignment does nothing
    {
      a_other.addRef();
      removeRef();
      m_data = a_other.m_data;
      m_refCount = a_other.m_refCount;
    }
    return *this;
  }

  ~ReferenceCountedObject()
  {
    removeRef();
  }

private:
  void addRef() const
  {
    ++m_refCount;
  }
  
  void removeRef()
  {
    if (--m_refCount)
    {
      delete[] m_data;
      m_data = nullptr;
    }
  }
  
  void modify()
  {
    if (m_refCount >= 2)
    {
      T* newData = m_data->clone();
      removeRef();
      m_data = newData;
      m_refCount.reset(); // detach from the original ref count
    }
  }

  T* m_data = nullptr;
  mutable ReferenceCount m_refCount;
};


// ReferenceCount objects need to be pointers, so there is an allocation there
// an alternative idea is to essentially make a doubly linked list of the shared objects
// When the linked list becomes empty, the item can be deleted.
template <typename T>
class ReferencedObject
{
public:
  // Default construct an object
  ReferencedObject()
  {
  }
  
  // Copy constructor
  explicit ReferencedObject(const ReferencedObject& a_other)
  {
    addRef(&a_other.m_refs);
    m_data = a_other.m_data;
  }
  
  // Assignment operator
  ReferencedObject& operator=(const ReferencedObject& a_other)
  {
    //! @todo what about m_data == a_other.m_data ???  Could it be conceptually a different thing and can we optimize this case?
    if (&a_other != this) // Self assignment does nothing
    {
      //! @todo not sure this is safe to remove before we have added the new reference
      removeRef();
      addRef(&a_other.m_refs);
      m_data = a_other.m_data;
    }
    return *this;
  }

  ~ReferencedObject()
  {
    removeRef();
  }

private:
  // There is this: https://en.wikipedia.org/wiki/XOR_linked_list
  // But it is no good for this use case as we need to remove items without iterating the entire list
  struct Pointers
  {
    Pointers* prev = nullptr;
    Pointers* next = nullptr;
  };

  void addRef(Pointers* a_refs) const
  {
    // insert this m_refs in to the chain of other
    m_refs.next = a_refs->next;
    a_refs->next = &m_refs;
    m_refs.prev = a_refs;
  }
  
  void removeRef()
  {
    Pointers* prev = m_refs.prev;
    if (prev) {
      assert(prev->next == &m_refs);
      prev->next = m_refs.next;
    }
    Pointers* next = m_refs.next;
    if (next) {
      assert(next->prev == &m_refs);
      next->prev = m_refs.prev;
    }
    if (next == nullptr && prev == nullptr)
    {
      delete[] m_data;
      m_data = nullptr;
    }
  }
  
  void modify()
  {
    if (m_refs.next != nullptr || m_refs.prev != nullptr)
    {
      T* newData = m_data->clone();
      removeRef();
      m_data = newData;
      m_refs.next = nullptr;
      m_refs.prev = nullptr;
      //m_refCount.reset(); // detach from the original ref count
    }
  }

  T* m_data = nullptr;
  mutable Pointers m_refs;
};


// Yet another idea is to allocate the reference count along with the object being reference counted
// Possible downside is that it may limit ability to have a polymorphic pointer
// Upcasts of T that has multiple inheritance
template <typename T>
class SmartObject
{
public:
  // Some kind of initial creation constructor that takes the args needed to construct a T
  template <typename ...Ts>
  explicit SmartObject(Ts ...args)
  {
    m_ptr = new InternalObject(args...);
  }
  
  // Copy constructor
  explicit SmartObject(const SmartObject<T>& a_other)
  {
    a_other.addRef();
    m_ptr = a_other.m_ptr;
    assert(m_ptr);
  }
 
  // Assignment operator
  SmartObject<T>& operator=(const SmartObject<T>& a_other)
  {
    // Self assignment should be safe
    a_other.addRef();
    removeRef(a_other.m_ptr);
    return *this;
  }

  ~SmartObject()
  {
    removeRef(nullptr);
  }

private:
  struct InternalObject
  {
    template <typename ...Ts>
    explicit InternalObject(Ts ...args) : m_data(args...), m_refCount(1) {}
    T       m_data;
    size_t  m_refCount;
  };

  void addRef()
  {
    assert(m_ptr);
    m_ptr->m_refCount++;
  }

  void removeRef(InternalObject *a_replace)
  {
    assert(m_ptr);
    if (!--m_ptr->m_refCount)
      delete m_ptr;
    m_ptr = a_replace;
  }

  InternalObject* m_ptr = nullptr;
};