This repository has been archived on 2025-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
CnC_Renegade/Code/Tools/wlib/arraylist.h

775 lines
19 KiB
C++

/*
** Command & Conquer Renegade(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU 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 General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/****************************************************************************\
* C O N F I D E N T I A L --- W E S T W O O D S T U D I O S *
******************************************************************************
Project Name:
File Name : arraylist.h
Author : Neal Kettler
Start Date : Jan 19, 1997
Last Update : Jan 19, 1997
------------------------------------------------------------------------------
Array implementation of a list. Note: There are some freaky C++ memory tricks
going on here. If you think there's a leak, see me before changing it.
The way this works is that it allocates an array to hold 'N' items on the
first list add. It doesn't call the constructors for those 'N' items until
necessary (for efficiency). When an item is added to a slot then a new
class is constructed inside the array element using the placement new operator
and the class's copy constructor. When elements are removed the destructor
is then manually called on this memory location.
All data added to the list is copied so feel free to delete/destroy/modify
the original after an add.
You _must_ have a good copy constructor for any classes that you use this template
for! A good copy constructor is one that won't blindly duplicate pointers
that don't belong to them, etc...
\****************************************************************************/
#ifndef ARRAYLIST_HEADER
#define ARRAYLIST_HEADER
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <new.h>
#include <math.h>
#include "wstypes.h"
//
// Usage: ArrayList<int> TheList;
//
template <class T>
class ArrayList
{
public:
ArrayList();
ArrayList(ArrayList<T> &other);
~ArrayList();
// Remove all entries from the lsit
void clear(void);
// Add a node after the zero based 'pos'
bit8 add(IN T &node,sint32 pos);
bit8 addTail(IN T &node);
bit8 addHead(IN T &node);
bit8 addSortedAsc(IN T &node); // Ascending
bit8 addSortedDes(IN T &node); // Descending
/*bit8 addNumSortedAsc(IN T &node); // Ascending
bit8 addNumSortedDes(IN T &node); // Descending*/
bit8 addMany(OUT T *outarray, sint32 pos, sint32 howmany);
// Remove a node
bit8 remove(OUT T &node,sint32 pos);
bit8 remove(sint32 pos);
bit8 removeHead(OUT T &node);
bit8 removeTail(OUT T &node);
sint32 removeMany(OUT T *outarray, sint32 pos, sint32 howmany);
// Replace one obj with another
bit8 replace(IN T &node, sint32 pos);
// Get a node without removing from the list
bit8 get(OUT T &node,sint32 pos) RO;
bit8 getHead(OUT T &node) RO;
bit8 getTail(OUT T &node) RO;
// Get a pointer to the interally managed copy (careful!)
bit8 getPointer(OUT T **node,sint32 pos) RO;
// Get the number of entries in the list
sint32 length(void) RO;
// UNSAFE! for classes, see note below!
bit8 setSize(sint32 newsize, IN T &filler);
// Print information on the list
void print(FILE *out);
// assignment operator
ArrayList<T> &operator=(IN ArrayList<T> &other);
private:
sint32 _sortedLookup(IN T &target, int ascending);
sint32 Entries_; // Number of entries
sint32 Slots_; // Number of available slots
T *Vector_; // The actual memory where the list is held
enum
{
INITIAL_SIZE = 10
};
bit8 growVector(void); // Expand the number of slots
bit8 shrinkVector(void); // Reduce the number of slots
};
//Create the empty list
template <class T>
ArrayList<T>::ArrayList()
{
Entries_=0;
Slots_=0;
Vector_=NULL;
}
// copy constructor
template <class T>
ArrayList<T>::ArrayList(ArrayList<T> &other)
{
Entries_=0;
Slots_=0;
Vector_=NULL;
(*this)=other;
}
//Free all the memory...
template <class T>
ArrayList<T>::~ArrayList()
{
clear(); // Remove the entries & call destructors on them
delete[]((uint8*)Vector_); // this will prevent the destructors from
// gettting called on elements not
// containing valid objects.
//fprintf(stderr,"Arraylist destructor\n");
}
// assignment operator
template <class T>
ArrayList<T> &ArrayList<T>::operator=(IN ArrayList<T> &other)
{
T node;
clear();
for (int i=0; i<other.length(); i++)
{
other.get(node,i);
addTail(node);
}
return(*this);
}
// Remove all the entries and free the memory
template <class T>
void ArrayList<T>::clear()
{
for (int i=0; i<Entries_; i++)
{
(Vector_+i)->~T(); // Call the destructor manually. Don't try this
// at home kiddies!
}
Entries_=0;
}
// ************************* UNSAFE UNSAFE UNSAFE *************************
// Note - Don't call this on any type with a constructor/destructor since this
// is really dumb and just puts a new one of filler in. Well, it's kindof safe
// just be careful.
// It's fine for simple classes like ints though..
//
// Add/remove entries in a stupid manner...
//
// **************************************************************************
template <class T>
bit8 ArrayList<T>::setSize(sint32 newsize, IN T &filler)
{
int oldEntries=Entries_;
Entries_ = newsize;
if (newsize<0)
return(false);
// Grow the vector as much as we need to
while (newsize > Slots_)
growVector();
// Create new objects in the blank holes
for (int i=oldEntries; i<Entries_; i++)
{
// Now put the replacement object in there...
new((void *)(Vector_+i)) T(filler); // Trust me, this isn't a memory leak
}
// If we're at 33% usage or less, shrink the vector
if ((Entries_*3) <= Slots_) // don't do while, because I think shrink will never goto 0
shrinkVector();
return(true);
}
// When adding into a position, the new node goes at the zero based slot
// specified by pos. All other nodes get moved one slot down.
template <class T>
bit8 ArrayList<T>::add(IN T &node,sint32 pos)
{
if (pos > Entries_) // You can only access one of the end of the vector
pos=Entries_;
if (pos >= Slots_) // If we're at the end, grow the list
growVector();
if (Entries_ >= Slots_) // enuff space?
growVector();
// If we are insering into the middle or front of the list we have to
// slide the old objects forward.
if (pos < Entries_) // If there are elements after the add point
memmove(Vector_+pos+1,Vector_+pos,sizeof(T)*(Entries_-pos)); // move them forward
//fprintf(stderr,"Placement new to %p\n",(Vector_+pos));
// This uses the placement new operator. placement new allows us to
// specify the memory address for the new object. In this case we
// want it at the 'pos' index into our array.
new((void *)(Vector_+pos)) T((T &)node); // Trust me, this isn't a memory leak
Entries_++; // one new entry
return(TRUE);
}
// Add to the first node, all others get shifted down one slot
template <class T>
bit8 ArrayList<T>::addHead(IN T &node)
{
return(add(node,0));
}
// Append to the end of the list
template <class T>
bit8 ArrayList<T>::addTail(IN T &node)
{
return(add(node,length()));
}
// addSortedX only works (properly) if evrerything else in the list is added
// using addSorted.
template <class T>
bit8 ArrayList<T>::addSortedAsc(IN T &node)
{
sint32 pos = _sortedLookup(node, 1);
return(add(node, pos));
}
// addSortedX only works (properly) if evrerything else in the list is added
// using addSorted.
template <class T>
bit8 ArrayList<T>::addSortedDes(IN T &node)
{
sint32 pos = _sortedLookup(node, 0);
return(add(node, pos));
}
// This is the binary search used by addSorted
template <class T>
sint32 ArrayList<T>::_sortedLookup(IN T &target, int ascending)
{
int low, mid, high;
T* lowtarget;
T* hightarget;
T* midtarget;
// Trivial cases
if( Entries_ == 0 )
return 0;
low = 0;
high = Entries_ - 1;
while( 1 )
{
assert( low <= high );
mid = low + (int)(floor(((double)high - (double)low) / (double)2));
getPointer(&lowtarget, low);
getPointer(&hightarget, high);
getPointer(&midtarget, mid);
// Exact match
if( *midtarget == target ) return mid;
// Single element
if( high == low )
{
if( ascending )
{
if( target <= *lowtarget )
return low;
else
return low + 1;
}
else
{
if( target <= *lowtarget )
return low + 1;
else
return low;
}
}
// Two elemsnts
if( (high - low) == 1 )
{
if( ascending )
{
if( target <= *lowtarget )
return low;
else if( target <= *hightarget )
return high;
else
return high + 1;
}
else
{
if( target <= *hightarget )
return high + 1;
else if( target <= *lowtarget )
return high;
else
return low;
}
}
// Sorry, try again...
if( ascending )
{
if( target < *midtarget )
high = mid;
else
low = mid;
}
else
{
if( target < *midtarget )
low = mid;
else
high = mid;
}
}
}
/*// addNumSortedX works in much the same way as addSortedX, except that I needed
// it for a very specific thing. I needed a list of strings numerically sorted,
// not alphabetically sorted. Furthermore these strings were composed of numbers
// delimited by underscores. In the interest of keeping it generic, these
// functions take as args a node, a delimiting character, and a count of the
// number of fields to include in a sort. If this is the list of strings:
//
// 55_100, 2_5, 23_32, 98_445, 2_48, 8_88, 2_3, 2_4
//
// An alphabetical sort is:
//
// 2_3, 2_4, 2_48, 2_5, 55_100, 8_88, 98_445
//
// But a numerical sort by calling addNumSortedAsc(<whatever>, "_", 2) will result in:
//
// 2_3, 2_4, 2_5, 2_48, 8_88, 55_100, 98_445
//
// Yes...now that you mention it I am on crack...
//
template <class T>
bit8 ArrayList<T>::addNumSortedAsc(IN T &node, char delim, int fields)
{
sint32 pos = _numSortedLookup(node, delim, fields, 1);
return(add(node, pos));
}
// See addNumSortedAsc comment above.
template <class T>
bit8 ArrayList<T>::addSortedDes(IN T &node, char delim, int fields)
{
sint32 pos = _sortedLookup(node, delim, fields, 0);
return(add(node, pos));
}
// This is the binary search used by addSorted
template <class T>
sint32 ArrayList<T>::_numSortedLookup(IN T &target, char delim, int fields, int ascending)
{
int low, mid, high;
T* lowtarget;
T* hightarget;
T* midtarget;
// Trivial case
if( Entries_ == 0 )
return 0;
low = 0;
high = Entries_;
while( 1 )
{
assert( low <= high );
mid = low + (int)(floor(((double)high - (double)low) / (double)2));
getPointer(&lowtarget, low);
getPointer(&hightarget, high);
getPointer(&midtarget, mid);
// Exact match
if( *midtarget == target ) return mid;
// Single element
if( high == low )
{
if( ascending )
{
if( target <= *lowtarget )
return low;
else
return low + 1;
}
else
{
if( target <= *lowtarget )
return low + 1;
else
return low;
}
}
// Two elemsnts
if( (high - low) == 1 )
{
if( ascending )
{
if( target <= *lowtarget )
return low;
else
return high;
}
else
{
if( target <= *lowtarget )
return high;
else
return low;
}
}
// Sorry, try again...
if( ascending )
{
if( target < *midtarget )
high = mid;
else
low = mid;
}
else
{
if( target < *midtarget )
low = mid;
else
high = mid;
}
}
}*/
//
// Delete an item at this index and construct a new one in it's place
//
template <class T>
bit8 ArrayList<T>::replace(IN T &node, sint32 pos)
{
if (Entries_==0)
return(FALSE);
if (pos<0)
pos=0;
if (pos >= Entries_)
pos=Entries_-1;
(Vector_+pos)->~T(); // Call the destructor manually. Don't try this
// at home kiddies!
// Now put the replacement object in there...
new((void *)(Vector_+pos)) T(node); // Trust me, this isn't a memory leak
return(TRUE);
}
// Remove at the zero based index specified by 'pos'. When removing from
// a slot, all others get shifted up by one.
template <class T>
bit8 ArrayList<T>::remove(sint32 pos)
{
if (Entries_==0)
return(FALSE);
if (pos<0)
pos=0;
if (pos >= Entries_)
pos=Entries_-1;
(Vector_+pos)->~T(); // Call the destructor manually. Don't try this
// at home kiddies!
memmove(Vector_+pos,Vector_+pos+1,sizeof(T)*(Entries_-pos-1));
Entries_--;
// If we're at 33% usage or less, shrink the vector
if ( (Entries_*3) <= Slots_)
shrinkVector();
return(TRUE);
}
// Remove at the zero based index specified by 'pos'. When removing from
// a slot, all others get shifted up by one.
template <class T>
bit8 ArrayList<T>::remove(OUT T &node, sint32 pos)
{
bit8 retval;
retval=get(node,pos);
if (retval==FALSE)
return(FALSE);
return(remove(pos));
}
// Remove the first node of the list
template <class T>
bit8 ArrayList<T>::removeHead(OUT T &node)
{
return(remove(node,0));
}
// Remove the last node of the list
template <class T>
bit8 ArrayList<T>::removeTail(OUT T &node)
{
return(remove(node,Entries_-1));
}
// get a pointer to the internally managed object. Try and avoid this, but
// sometimes efficiency requires it...
// get a copy of an item
template <class T>
bit8 ArrayList<T>::getPointer(OUT T **node,sint32 pos) RO
{
if ((pos < 0)||(pos >= Entries_))
return(FALSE);
*node=&(Vector_[pos]);
return(TRUE);
}
// get a copy of an item
template <class T>
bit8 ArrayList<T>::get(OUT T &node,sint32 pos) RO
{
if ((pos < 0)||(pos >= Entries_))
return(FALSE);
node=Vector_[pos];
return(TRUE);
}
// get a copy of the first node of the list
template <class T>
bit8 ArrayList<T>::getHead(OUT T &node) RO
{
return(get(node,0));
}
// get a copy of the last node
template <class T>
bit8 ArrayList<T>::getTail(OUT T &node) RO
{
return(get(node,Entries_-1));
}
// just for debugging
template <class T>
void ArrayList<T>::print(FILE *out)
{
fprintf(out,"--------------------\n");
//for (int i=0; i<Entries_; i++)
// Vector_[i].print();
fprintf(out,"Entries: %d Slots: %d sizeof(T): %d\n",Entries_,Slots_,
sizeof(T));
fprintf(out,"--------------------\n");
}
// Return the current length of the list
template <class T>
sint32 ArrayList<T>::length(void) RO
{
return(Entries_);
}
// Grow the vector by a factor of 2X
template <class T>
bit8 ArrayList<T>::growVector(void)
{
if (Entries_ < Slots_) // Don't grow until we're at 100% usage
return(FALSE);
int newSlots=Entries_*2;
if(newSlots < INITIAL_SIZE)
newSlots=INITIAL_SIZE;
//fprintf(stderr,"Growing vector to: %d\n",newSlots);
// The goofy looking new below prevents operator new from getting called
// unnecessarily. This is severall times faster than allocating all of
// the slots as objects and then calling the assignment operator on them
// when they actually get used.
//
T *newVector=(T *)(new uint8[newSlots * sizeof(T)]);
memset(newVector,0,newSlots * sizeof(T)); // zero just to be safe
if (Vector_ != NULL)
memcpy(newVector,Vector_,Entries_*sizeof(T));
delete[]((uint8 *)Vector_); // Get rid of the old vector without calling
// destructors
Vector_=newVector;
Slots_=newSlots;
return(TRUE);
}
// Shrink the vector by a factor of 2X
template <class T>
bit8 ArrayList<T>::shrinkVector(void)
{
//fprintf(stderr,"Shrink called\n");
// Don't need to shrink until usage goes below 33%
if ( (Entries_*3) > Slots_)
return(FALSE);
int newSlots=Slots_/2;
if(newSlots < INITIAL_SIZE) // never shrink past initial size
newSlots=INITIAL_SIZE;
if (newSlots >= Slots_) // don't need to shrink
return(FALSE);
//fprintf(stderr,"Shrinking vector to: %d\n",newSlots);
// The goofy looking new below prevents operator new from getting called
// unnecessarily. This is severall times faster than allocating all of
// the slots as objects and then calling the assignment operator on them
// when they actually get used.
//
T *newVector=(T *)(new uint8[newSlots * sizeof(T)]);
if (Vector_ != NULL) // Vector_ better not be NULL!
memcpy(newVector,Vector_,Entries_*sizeof(T));
delete[]((uint8 *)Vector_); // Get rid of the old vector without calling
// destructors
Vector_=newVector;
Slots_=newSlots;
return(TRUE);
}
// Implementation was missing from source code. This function is based on the build
// artifact DLL in the archive and similar functions in this header file.
// Compiles, but not tested. LFeenanEA - January 27th 2025
template <class T>
sint32 ArrayList<T>::removeMany(OUT T *outarray, sint32 pos, sint32 howmany)
{
if (Entries_==0)
return(FALSE);
if (pos<0)
pos=0;
if (pos>=Entries_)
pos=Entries_-1;
if (pos+howmany>Entries_)
assert(0);
for (int i=0; i<howmany; i++)
{
if (outarray)
outarray[i]=Vector_[pos+i];
}
memmove(Vector_+pos,Vector_+pos+howmany,sizeof(T)*(Entries_-pos-howmany));
Entries_-=howmany;
// If we're at 33% usage or less, shrink the vector
if ((Entries_*3) <= Slots_) // don't do while, because I think shrink will never goto 0
shrinkVector();
return(howmany);
}
// Implementation was missing from source code. This function is based on the build
// artifact DLL in the archive and similar functions in this header file.
// Compiles, but not tested. LFeenanEA - January 27th 2025
template <class T>
bit8 ArrayList<T>::addMany(IN T *inarray, sint32 pos, sint32 howmany)
{
if (pos > Entries_) // You can only access one of the end of the vector
pos=Entries_;
// Grow the vector as much as we need to
while (howmany+Entries_ >= Slots_)
growVector();
// If we are insering into the middle or front of the list we have to
// slide the old objects forward.
if (pos < Entries_) // If there are elements after the add point
memmove(Vector_+pos+howmany,Vector_+pos,sizeof(T)*(Entries_-pos)); // move them forward
// This uses the placement new operator. placement new allows us to
// specify the memory address for the new object. In this case we
// want it at the 'pos' index into our array.
for (int i=0; i<howmany; i++)
{
new((void *)(Vector_+i)) T(inarray[i]); // Trust me, this isn't a memory leak
}
Entries_+=howmany;
return(TRUE);
}
#endif