/
clans/
include/CVS/
manual/CVS/
races/CVS/
system/CVS/
text/
text/CVS/
todo/
todo/CVS/
units/CVS/
unit dtypes;

interface

uses
    SysUtils,
    SyncObjs;

type
    GListNode = class
      prev, next : GListNode;
      element : pointer;
      refcount : integer;

      constructor Create(e : pointer; p, n : GListNode);
    end;

    GDLinkedList = class
      size : integer;
      head, tail : GListnode;
      lock : TCriticalSection;

      function insertLast(element : pointer) : GListNode;
      function insertAfter(tn : GListNode; element : pointer) : GListNode;
      function insertBefore(tn : GListNode; element : pointer) : GListNode;
      procedure remove(node : GListNode);
      procedure clean;
      procedure smallClean;

      function getSize : integer;

      constructor Create;
      destructor Destroy; override;
    end;

    GPrimes = array of integer;

    GString = class
      refcount : integer;
      value : string;

      constructor Create(s : string);
    end;

    GHASH_FUNC = function(size, prime : cardinal; key : string) : integer;

    GHashTable = class
      hashsize : cardinal;
      hashprime : cardinal;

      bucketList : array of GDLinkedList;

      hashFunc : GHASH_FUNC;

      function getUsed : integer;
      function getHash(key : string) : integer;
      procedure setHashFunc(func : GHASH_FUNC);
      function findPrimes(n : integer) : GPrimes;

      procedure hashStats; virtual;
      procedure hashPointer(ptr : pointer; key : string);

      constructor Create(size : integer);
      destructor Destroy; override;
    end;

    GHashObject = class(GHashTable)
      procedure hashObject(obj : TObject; key : string);
    end;

    GHashString = class(GHashTable)
      procedure hashString(str : GString; key : string);
      procedure hashStats; override;
    end;

    GException = class(Exception)
      e_location : string;

      constructor Create(location, msg : string);
      procedure show;
    end;

const STR_HASH_SIZE = 1024;

var
   str_hash : GHashString;

function hash_string(src : string) : PString; overload;
function hash_string(src : PString) : PString; overload;

procedure unhash_string(var src : PString);


function defaultHash(size, prime : cardinal; key : string) : integer;
function firstHash(size, prime : cardinal; key : string) : integer;

implementation

uses
    mudsystem;

// GListNode
constructor GListNode.Create(e : pointer; p, n : GListNode);
begin
  inherited Create;

  element := e;
  next := n;
  prev := p;
  refcount := 1;
end;


// GDLinkedList
constructor GDLinkedList.Create;
begin
  inherited Create;

  head := nil;
  tail := nil;
  size := 0;
  lock := TCriticalSection.Create;
end;

destructor GDLinkedList.Destroy;
begin
  lock.Free;

  inherited Destroy;
end;

function GDLinkedList.insertLast(element : pointer) : GListNode;
var
   node : GListNode;
begin
  try
    lock.Acquire;

    node := GListNode.Create(element, tail, nil);

    if (head = nil) then
      head := node
    else
      tail.next := node;

    tail := node;

    insertLast := node;

    inc(size);
  finally
    lock.Release;
  end;
end;

function GDLinkedList.insertAfter(tn : GListNode; element : pointer) : GListNode;
var
   node : GListNode;
begin
  try
    lock.Acquire;

    node := GListNode.Create(element, tn, tn.next);

    if (tn.next <> nil) then
      tn.next.prev := node;
      
    tn.next := node;

    if (tail = tn) then
      tail := node;

    insertAfter := node;

    inc(size);
  finally
    lock.Release;
  end;
end;

function GDLinkedList.insertBefore(tn : GListNode; element : pointer) : GListNode;
var
   node : GListNode;
begin
  try
    lock.Acquire;

    node := GListNode.Create(element, tn.prev, tn);

    if (tn.prev <> nil) then
      tn.prev.next := node;
      
    tn.prev := node;

    if (head = tn) then
      head := node;

    insertBefore := node;

    inc(size);
  finally
    lock.Release;
  end;
end;

procedure GDLinkedList.remove(node : GListNode);
begin
  try
    lock.Acquire;

    if (node.prev = nil) then
      head := node.next
    else
      node.prev.next := node.next;

    if (node.next = nil) then
      tail := node.prev
    else
      node.next.prev := node.prev;

    dec(size);
    node.Free;
  finally
    lock.Release;
  end;
end;

function GDLinkedList.getSize : integer;
begin
  getSize := size;
end;

procedure GDLinkedList.clean;
var
   node : GListNode;
begin
  while (true) do
    begin
    node := tail;

    if (node = nil) then
      exit;

    TObject(node.element).Free;

    remove(node);
    end;
end;

// doesn't free elements
procedure GDLinkedList.smallClean;
var
   node : GListNode;
begin
  while (true) do
    begin
    node := head;

    if (node = nil) then
      exit;

    remove(node);
    end;
end;


// GString
constructor GString.Create(s : string);
begin
  inherited Create;

  value := s;
end;


// GHashTable
function defaultHash(size, prime : cardinal; key : string) : integer;
var
   i : integer;
   val : cardinal;
begin
  val := 0;

  for i := 1 to length(key) do
    val := val * prime + byte(key[i]);

  defaultHash := val mod size;
end;

function firstHash(size, prime : cardinal; key : string) : integer;
begin
  if (length(key) >= 1) then
    Result := (byte(key[1]) * prime) mod size
  else
    Result := 0;
end;


function GHashTable.findPrimes(n : integer) : GPrimes;
var
   i, j : integer;
   limit : double;
   numbers : GPrimes;
   numberpool : array of boolean;
begin
  setlength(numberpool, n);

  for i := 2 to n - 1 do
    numberpool[i] := true;

  limit := sqrt(n);

  j := 2;

  i := j + j;

  while (i < n) do
    begin
    numberpool[i] := false;
    i := i + j;
    end;

  j := 3;

  while (j <= limit) do
    begin
    if (numberpool[j] = true) then
      begin
      i := j + j;

      while (i < n) do
        begin
        numberpool[i] := false;

        i := i + j;
        end;
      end;

    j := j + 2;
    end;

  j := 0;

  for i := 0 to n - 1 do
    begin
    if (numberpool[i]) then
      begin
      setLength(numbers, j + 1);
      numbers[j] := i;
      j := j + 1;
      end;
    end;

  findPrimes := numbers;
end;

function GHashTable.getHash(key : string) : integer;
begin
  Result := hashFunc(hashsize, hashprime, key);
end;

procedure GHashTable.setHashFunc(func : GHASH_FUNC);
begin
  hashFunc := func;
end;

procedure GHashTable.hashPointer(ptr : pointer; key : string);
var
   hash : integer;
begin
  hash := getHash(key);

  bucketList[hash].insertLast(ptr);
end;

function GHashTable.getUsed : integer;
var
   i : integer;
   total : integer;
begin
  total := 0;

  for i := 0 to hashsize - 1 do
    begin
    total := total + bucketList[i].getSize;
    end;

  getUsed := total;
end;

procedure GHashTable.hashStats;
var
   i : integer;
   total : integer;
   load : single;
   min, max : integer;
begin
  total := 0;
  min := 65536;
  max := 0;

  for i := 0 to hashsize - 1 do
    begin
    total := total + bucketList[i].getSize;

    if (bucketList[i].getSize < min) then
      min := bucketList[i].getSize;
    if (bucketList[i].getSize > max) then
      max := bucketList[i].getSize;
    end;

  load := total / hashsize;

  writeln('Hash size ' + inttostr(hashsize) + ' with key ' + inttostr(hashprime));
  writeln('Total hash items : ' + inttostr(total));
  writeln('Load factor : ' + floattostrf(load, ffFixed, 7, 4));
end;

constructor GHashTable.Create(size : integer);
var
   n : integer;
   primes : GPrimes;
begin
  inherited Create;

  primes := findPrimes(size + 32);

  randomize;
  hashsize := primes[length(primes) - 1];
  hashprime := primes[random(length(primes))];

  setlength(bucketList, hashsize);

  for n := 0 to hashsize - 1 do
    bucketList[n] := GDLinkedList.Create;

  hashFunc := defaultHash;
end;

destructor GHashTable.Destroy;
var
   n : integer;
begin
  for n := 0 to hashsize - 1 do
    begin
    bucketList[n].clean;
    bucketList[n].Free;
    end;

  setlength(bucketList, 0);

  inherited Destroy;
end;


// GHashObject
procedure GHashObject.hashObject(obj : TObject; key : string);
begin
  hashPointer(obj, key);
end;


// GHashString
procedure GHashString.hashString(str : GString; key : string);
begin
  hashPointer(str, key);
end;

procedure GHashString.hashStats;
var
   bytesused, wouldhave : integer;
   i, s : cardinal;
   node : GListNode;
begin
  inherited hashStats;

  bytesused := 0;
  wouldhave := 0;

  for i := 0 to hashsize - 1 do
    begin
    node := bucketList[i].head;

    while (node <> nil) do
      begin
      s := length(GString(node.element).value);

      bytesused := bytesused + (s + 1);
      wouldhave := wouldhave + (node.refcount * (s + 1));

      node := node.next;
      end;
    end;

  writeln('Byte savings (used/saved): ', inttostr(bytesused), '/', inttostr(wouldhave - bytesused), ' (', (bytesused * 100) div wouldhave, '%/', ((wouldhave - bytesused) * 100) div wouldhave, '%)');
end;

function hash_string(src : string) : PString;
var
   hash : integer;
   node, fnode : GListNode;
   g : GString;
begin
  hash := str_hash.getHash(src);

  node := str_hash.bucketList[hash].head;
  fnode := nil;

  while (node <> nil) do
    begin
    if (comparestr(GString(node.element).value, src) = 0) then
      begin
      fnode := node;
      break;
      end;

    node := node.next;
    end;

  if (fnode <> nil) then
    begin
    g := fnode.element;
    hash_string := @g.value;
    fnode.refcount := fnode.refcount + 1;
    end
  else
    begin
    g := GString.Create(src);
    str_hash.bucketList[hash].insertLast(g);
    hash_string := @g.value;
    end;
end;

function hash_string(src : PString) : PString;
var
   hash : integer;
   node, fnode : GListNode;
   g : GString;
begin
  hash := str_hash.getHash(src^);

  node := str_hash.bucketList[hash].head;
  fnode := nil;

  while (node <> nil) do
    begin
    if (comparestr(GString(node.element).value, src^) = 0) then
      begin
      fnode := node;
      break;
      end;

    node := node.next;
    end;

  if (fnode <> nil) then
    begin
    g := fnode.element;
    hash_string := @g.value;
    fnode.refcount := fnode.refcount + 1;
    end
  else
    begin
    g := GString.Create(src^);
    str_hash.bucketList[hash].insertLast(g);
    hash_string := @g.value;
    end;
end;

procedure unhash_string(var src : PString);
var
   hash : integer;
   node, fnode : GListNode;
   g : GString;
begin
  if (src = nil) then
    exit;

  hash := str_hash.getHash(src^);
    
  node := str_hash.bucketList[hash].head;
  fnode := nil;

  while (node <> nil) do
    begin
    if (comparestr(GString(node.element).value, src^) = 0) then
      begin
      fnode := node;
      break;
      end;

    node := node.next;
    end;

  if (fnode <> nil) then
    begin
    dec(fnode.refcount);

    if (fnode.refcount <= 0) then
      begin
      GString(fnode.element).Free;
      str_hash.bucketList[hash].remove(fnode);
      end;

    src := nil;
    end;
end;

// GException
constructor GException.Create(location, msg : string);
begin
  inherited Create(msg);
end;

procedure GException.show;
begin
  write_console('Exception ' + Message + ' @ ' + e_location);
end;


begin
  str_hash := GHashString.Create(STR_HASH_SIZE);
end.