//
// File: hzTmplArray.h
//
// Legal Notice: This file is part of the HadronZoo C++ Class Library.
//
// Copyright 2025 HadronZoo Project (http://www.hadronzoo.com)
//
// The HadronZoo C++ Class Library is free software: You can redistribute it, and/or modify it under the terms of the GNU Lesser General Public License, as published by the Free
// Software Foundation, either version 3 of the License, or any later version.
//
// The HadronZoo C++ Class Library 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License along with the HadronZoo C++ Class Library. If not, see http://www.gnu.org/licenses.
//
#ifndef hzTmplArray_h
#define hzTmplArray_h
#include "hzLock.h"
#include "hzProcess.h"
/*
** Prototypes
*/
void Fatal (const char* va_alist ...) ;
#define HZ_ARRAY_NODESIZE 8
template<class OBJ> class hzArray
{
// Category: Object Collection
//
// The hzArray class template facilitates an array of objects. The objects are not stored in an actual array (of continuous memory) but in a tree of blocks
// based on absolute positioning. On the highest level the data blocks comprise only an array of HZ_ARRAY_NODESIZE objects while on the inner levels the index
// blocks comprise nothing but HZ_ARRAY_NODESIZE pointers to the blocks above. With a population of 0 the root of the tree is null and the hzArray has a level
// of zero. With a population between 1 and HZ_ARRAY_NODESIZE, the root points to a single data block and the hzArray now has 1 level. As the population rises
// above HZ_ARRAY_NODESIZE the level goes up to 2 and from now on the root is an index node. A new level is created when the root splits into two nodes, which
// happens when the population goes above HZ_ARRAY_NODESIZE to the power of the current level.
//
// Note that hzArray does not support an Insert() or a Delete() method. It is intended only to be aggregated to by a series of appends and then accesses by
// the [] operator. The hzArray is thus an unordered collection.
struct _hz_ar_data
{
// Block of HZ_ARRAY_NODESIZE objects
OBJ m_Objs[HZ_ARRAY_NODESIZE] ; // Objects held in the array
} ;
struct _hz_ar_indx
{
// Array of pointers (to either index or data blocks)
void* m_Ptrs[HZ_ARRAY_NODESIZE] ; // The pointers
} ;
struct _array_ca
{
// Array control area
hzLockRW m_Lock ; // Read/Write lock
void* m_pRoot ; // Root node
uint32_t m_nFactor ; // 1 for level 0, HZ_ARRAY_NODESIZE for level 1, HZ_ARRAY_NODESIZE squared for 2 etc
uint32_t m_nNoDN ; // Number of data nodes
uint32_t m_nNoIN ; // Number of index nodes
uint32_t m_nCount ; // Number of items
uint32_t m_nLevel ; // The level of the array
//_mut uint32_t m_nCopy ; // Number of copies
OBJ m_Default ; // Default (empty) element
//_array_ca (void) { m_pRoot = 0 ; m_nLevel = m_nNoDN = m_nNoIN = m_nCount = m_nCopy = 0 ; m_nFactor = 1 ; }
_array_ca (void) { m_pRoot = 0 ; m_nLevel = m_nNoDN = m_nNoIN = m_nCount = 0 ; m_nFactor = 1 ; }
~_array_ca (void) {}
void Lock (void) { m_Lock.LockWrite() ; }
void Unlock (void) { m_Lock.Unlock() ; }
void KillLock (void) { m_Lock.Kill() ; }
} ;
void _clear (void* pN, uint32_t nLevel, uint32_t maxLevel)
{
_hz_ar_indx* pIdx ; // Current index block pointer
_hz_ar_data* pDN ; // Current object block pointer
uint32_t n ; // Object block iterator
if (!mx)
return ;
if (nLevel < maxLevel)
{
pIdx = (_hz_ar_indx*) pN ;
for (n = 0 ; n < HZ_ARRAY_NODESIZE && pIdx->m_Ptrs[n] ; n++)
_clear(pIdx->m_Ptrs[n], nLevel+1, maxLevel) ;
}
else
{
pDN = (_hz_ar_data*) pN ; //pIdx->m_Ptrs[n] ;
delete pDN ;
}
}
_array_ca* mx ; // The actual array
// Prevent copies
hzArray<OBJ> (hzArray<OBJ>& op) ;
hzArray<OBJ>& operator= (const hzArray<OBJ>& op) ;
public:
hzArray (void)
{
_hzGlobal_Memstats.m_numArrays++ ;
mx = new _array_ca() ;
}
~hzArray (void)
{
if (mx)
{
mx->Lock() ;
/*
if (mx->m_nCopy)
{
// Reduce copy count
mx->m_nCopy-- ;
mx->Unlock() ;
}
else
{
*/
mx->KillLock() ;
// Clear Root
if (mx->m_pRoot)
_clear(mx->m_pRoot, 1, mx->m_nLevel) ;
_hzGlobal_Memstats.m_numArrayDA-- ;
delete mx ;
//}
}
_hzGlobal_Memstats.m_numArrays-- ;
}
void SetDefault (OBJ dflt) { mx->m_Default = dflt ; }
void Clear (void)
{
// Clear all nodes
_hzfunc("hzArray::Clear") ;
if (mx)
{
mx->Lock() ;
if (mx->m_pRoot)
_clear(mx->m_pRoot, 1, mx->m_nLevel) ;
mx->m_pRoot = 0 ;
mx->m_nLevel = 0 ;
mx->m_nCount = 0 ;
mx->m_nFactor = 1 ;
mx->Unlock() ;
}
}
hzEcode Add (const OBJ& obj)
{
// Add (append) an object to the end of the collection
//
// Arguments: 1) obj Object to add
_hzfunc("hzArray::Add") ;
_hz_ar_data* pDN ; // Data node
_hz_ar_indx* pIdx ; // Index node
uint32_t x ; // Position so far
uint32_t n ; // Offest at current level
uint32_t f ; // Division factor
uint32_t dnAddr ; // Slot on higest index node for target data node
uint32_t nSlot ; // Target slot on data node
if (!mx)
mx = new _array_ca() ;
mx->Lock() ;
// If no root
if (!mx->m_pRoot)
{
mx->m_nNoDN = 1 ;
pDN = new _hz_ar_data() ;
pDN->m_Objs[0] = obj ;
mx->m_pRoot = pDN ;
mx->m_nLevel = 1 ;
mx->m_nCount = 1 ;
mx->Unlock() ;
return E_OK ;
}
if (mx->m_nCount < HZ_ARRAY_NODESIZE)
{
// If root is a data node
pDN = (_hz_ar_data*) mx->m_pRoot ;
pDN->m_Objs[mx->m_nCount] = obj ;
mx->m_nCount++ ;
mx->Unlock() ;
return E_OK ;
}
// Do we need a new level?
if (mx->m_nCount == (mx->m_nFactor * HZ_ARRAY_NODESIZE))
{
mx->m_nNoIN++ ;
pIdx = new _hz_ar_indx() ;
pIdx->m_Ptrs[0] = mx->m_pRoot ;
mx->m_pRoot = pIdx ;
mx->m_nLevel++ ;
mx->m_nFactor *= HZ_ARRAY_NODESIZE ;
}
// Go to highest data node
pIdx = (_hz_ar_indx*) mx->m_pRoot ;
for (x = mx->m_nCount, f = mx->m_nFactor ; f >= (HZ_ARRAY_NODESIZE * HZ_ARRAY_NODESIZE) ;)
{
n = x/f ; x %= f ; f /= HZ_ARRAY_NODESIZE ;
if (!pIdx->m_Ptrs[n])
{ mx->m_nNoIN++ ; pIdx->m_Ptrs[n] = new _hz_ar_indx() ; }
pIdx = (_hz_ar_indx*) pIdx->m_Ptrs[n] ;
}
// Data node 'address' is
dnAddr = x/f ;
if (!pIdx->m_Ptrs[dnAddr])
{ mx->m_nNoDN++ ; pIdx->m_Ptrs[dnAddr] = new _hz_ar_data() ; }
pDN = (_hz_ar_data*) pIdx->m_Ptrs[dnAddr] ;
// Data node slot is remainder
nSlot = x%f ;
pDN->m_Objs[nSlot] = obj ;
mx->m_nCount++ ;
mx->Unlock() ;
return E_OK ;
}
OBJ& operator[] (uint32_t nPosn) const
{
_hzfunc("hzArray::operator[]") ;
_hz_ar_indx* pIdx ; // Index node pointer
_hz_ar_data* pDN ; // Index node pointer
uint32_t x ; // Position reached so far
uint32_t n ; // Offest at current level
uint32_t f ; // Division factor
if (nPosn >= mx->m_nCount)
return mx->m_Default ;
f = mx->m_nFactor ;
if (f == 1)
{
pDN = (_hz_ar_data*) mx->m_pRoot ;
n = nPosn ;
return pDN->m_Objs[nPosn] ;
}
pIdx = (_hz_ar_indx*) mx->m_pRoot ;
for (x = nPosn, f = mx->m_nFactor ; f >= (HZ_ARRAY_NODESIZE * HZ_ARRAY_NODESIZE) ;)
{
n = x/f ; x %= f ; f /= HZ_ARRAY_NODESIZE ;
pIdx = (_hz_ar_indx*) pIdx->m_Ptrs[n] ;
}
pDN = (_hz_ar_data*) pIdx->m_Ptrs[x/f] ;
return pDN->m_Objs[x%f] ;
}
OBJ* InSitu (uint32_t nPosn) const
{
// Return pointer to the actual in-situ object
_hzfunc("hzArray::InSitu") ;
_hz_ar_indx* pIdx ; // Index node pointer
_hz_ar_data* pDN ; // Index node pointer
OBJ* pObj ; // Object pointer
uint32_t x ; // Position reached so far
uint32_t n ; // Offest at current level
uint32_t f ; // Division factor
if (nPosn >= mx->m_nCount)
return 0 ; //mx->m_Default ;
f = mx->m_nFactor ;
if (f == 1)
{
pDN = (_hz_ar_data*) mx->m_pRoot ;
pObj = (OBJ*) pDN->m_Objs ;
return pObj + nPosn ;
}
pIdx = (_hz_ar_indx*) mx->m_pRoot ;
for (x = nPosn, f = mx->m_nFactor ; f >= (HZ_ARRAY_NODESIZE * HZ_ARRAY_NODESIZE) ;)
{
n = x/f ; x %= f ; f /= HZ_ARRAY_NODESIZE ;
pIdx = (_hz_ar_indx*) pIdx->m_Ptrs[n] ;
}
pDN = (_hz_ar_data*) pIdx->m_Ptrs[x/f] ;
pObj = (OBJ*) pDN->m_Objs ;
return pObj + (x%f) ;
}
// Diagnostics
uint32_t Level (void) const { return mx ? mx->m_nLevel : 0 ; }
uint32_t Factor (void) const { return mx ? mx->m_nFactor : 0 ; }
uint32_t dBlocks (void) const { return mx ? mx->m_nNoDN : 0 ; }
uint32_t iBlocks (void) const { return mx ? mx->m_nNoIN : 0 ; }
uint32_t Count (void) const { return mx ? mx->m_nCount : 0 ; }
} ;
#endif // hzTmplArray_h