unit LuaThread;

{
This unit contains the class used to control the threads spawned by lua
}

{$mode delphi}

interface

uses
  {$ifdef darwin}
  macport,
  {$endif}
  {$ifdef windows}
  windows, comobj,
  {$endif}
  Classes, SysUtils,lua, lualib, lauxlib, LuaHandler, syncobjs,
  SyncObjs2;

procedure initializeLuaThread;

implementation

uses LuaClass, LuaObject;

resourcestring
  rsErrorInNativeThreadCalled = 'Error in native thread called ';
  rsInNativeCode = ' in native code:';
  rsInvalidFirstParameterForCreateNativeThread = 'Invalid first parameter for createNativeThread';

type TCEThread=class (TThread)
  private
    fname: string;
    functionid: integer;
    L: PLua_State;
  public
    syncfunction: integer;
    syncparam: integer;
    syncparamcount: integer;
    procedure sync; //called by lua_synchronize from inside the thread
    procedure execute; override;
    destructor destroy; override;
    constructor create(L: Plua_State; functionid: integer; suspended: boolean);
  published
    property name: string read fname write fname;
    property Terminated;
    property Finished;
end;

procedure TCEThread.sync;
var
  paramcount: integer;
  i: integer;
begin
  //call the lua function
  lua_rawgeti(L, LUA_REGISTRYINDEX, syncfunction);
  luaclass_newclass(L, self);

  if syncparam>0 then
  begin
    if syncparamcount=0 then
      syncparamcount:=1;

    for i:=0 to syncparamcount-1 do
      lua_pushvalue(L, syncparam+i);

    paramcount:=1+syncparamcount;
  end
  else
  begin
    lua_pushnil(L);
    paramcount:=2;
  end;

  lua_pcall(L, paramcount,1,0);
end;

procedure TCEThread.execute;
var errorstring: string;
  extraparamcount: integer;
begin
  //call the lua function
  {$ifdef windows}
  CoInitializeEx(nil,0);
  {$endif}

  try
    extraparamcount:=lua_gettop(L);

    lua_rawgeti(L, LUA_REGISTRYINDEX, functionid);

    lua_insert(L, 1);



    luaclass_newClass(L, self);
    lua_insert(L, 2);

    if lua_pcall(L, 1+extraparamcount,0,0)<>0 then
    begin
      if lua_isstring(L, -1) then
        errorstring:=':'+Lua_ToString(L,-1)
      else
        errorstring:='';

      lua_getglobal(L, 'print');
      lua_pushstring(L, rsErrorInNativeThreadCalled+name+':'+errorstring);
      lua_pcall(L, 1,0,0);

    end;
  except
    on e:Exception do
    begin
      lua_getglobal(L, 'print');
      lua_pushstring(L, rsErrorInNativeThreadCalled+name+rsInNativeCode+e.Message);
      lua_pcall(L, 1,0,0);
    end;
  end;

  OutputDebugString('Lua thread terminated');
end;

destructor TCEThread.destroy;
begin
  //dereference the function
  luaL_unref(L, LUA_REGISTRYINDEX, functionid);

  lua_pushnil(L);
  lua_setglobal(L, pchar('CELUATHREAD_'+IntToHex(ptruint(L),8)));

  inherited destroy;
end;

constructor TCEThread.create(L: PLua_state; functionid: integer; suspended: boolean);
begin
  self.l:=l;
  self.functionid:=functionid;
  name:='Unnamed';

  inherited create(suspended);
end;




function createNativeThreadInternal(L: PLua_State; suspended: boolean): integer; cdecl;
var
  f: integer;
  routine: string;

  c: TCEThread;

  newL: Plua_State;
  s: string;

  paramcount: integer;
  i,v: integer;
begin
  result:=0;

  paramcount:=lua_gettop(L);

  if paramcount>=1 then
  begin
    if lua_isfunction(L,1) then
    begin
      lua_pushvalue(L, 1);
      f:=luaL_ref(L,LUA_REGISTRYINDEX);

    end
    else
    if lua_isstring(L,1) then
    begin
      routine:=lua_tostring(L,1);
      //get a reference to this function

      lua_getglobal(L, pchar(routine));
      f:=luaL_ref(L,LUA_REGISTRYINDEX);
    end
    else
      raise exception.create(rsInvalidFirstParameterForCreateNativeThread);

    newL:=lua_newthread(L);

    s:='CELUATHREAD_'+IntToHex(ptruint(newL),8);
    lua_setglobal(L, pchar(s));

    lua_sethook(newL, nil, 0, 0);   //no debugging on this thread for now

    //copy the extra parameters from the original call to the new one
    for i:=2 to paramcount do
    begin
      //get a ref to this parameter
      lua_pushvalue(L, i);
      v:=luaL_ref(L,  LUA_REGISTRYINDEX);
      lua_rawgeti(newL,LUA_REGISTRYINDEX,  v);

      lua_unref(L, v);
    end;

    //clear the stack  (just in case)
    lua_pop(L, lua_gettop(L));



    c:=TCEThread.create(newL, f, true);
    luaclass_newClass(L, c);
    result:=1;

    c.FreeOnTerminate:=true;

    if not suspended then
      c.Start;
  end
  else
    lua_pop(L, lua_gettop(L));
end;

//Lua functions

function luaCreateCriticalSection(L: PLua_State): integer; cdecl;
var cs: TCriticalSection;
begin
  cs:=TCriticalSection.Create;
  luaclass_newclass(L, cs);
  result:=1;
end;

function luaCreateEvent(L: PLua_State): integer; cdecl;
var e: TEvent;
begin
  result:=0;
  if lua_gettop(L)=2 then
  begin
    e:=TEvent.Create(nil, lua_toboolean(L,1), lua_toboolean(L,2), '');
    luaclass_newclass(L, e);
    result:=1;
  end;
end;

function luaCreateSemaphore(L: PLua_State): integer; cdecl;
var s: TSemaphore;
begin
  result:=0;
  if lua_gettop(L)=1 then
  begin
    s:=TSemaphore.Create(lua_tointeger(L,1));
    luaclass_newclass(L, s);
    result:=1;
  end;
end;

function luaCreateMultiReadExclusiveWriteSynchronizer(L: PLua_State): integer; cdecl;
var mrew: TMultiReadExclusiveWriteSynchronizer;
begin
  mrew:=TMultiReadExclusiveWriteSynchronizer.Create;
  luaclass_newclass(L, mrew);
  result:=1;
end;

function lua_getCPUCount(L: PLua_State): integer; cdecl;
begin
  result:=1;
  lua_pushinteger(L, GetCPUCount);
end;

function createNativeThread(L: PLua_State): integer; cdecl;
begin
  result:=createNativeThreadInternal(L, false);
end;

function createNativeThreadSuspended(L: PLua_State): integer; cdecl;
begin
  result:=createNativeThreadInternal(L, true);
end;

function thread_freeOnTerminate(L: PLua_State): integer; cdecl;
var
  c: TCEThread;
begin
  result:=0;
  c:=luaclass_getClassObject(L);
  if lua_gettop(L)>=1 then
    c.FreeOnTerminate:=lua_toboolean(L, 1);
end;

function thread_synchronize(L: PLua_State): integer; cdecl;
var
  f: integer;
  routine: string;

  c: TCEThread;

  newL: Plua_State;
  paramstart, paramcount: integer;
begin
  result:=0;
  c:=luaclass_getClassObject(L, @paramstart, @paramcount);


  if paramcount>=1 then
  begin
    if lua_isfunction(L,paramstart) then
    begin
      lua_pushvalue(L, paramstart);
      f:=luaL_ref(L,LUA_REGISTRYINDEX);

    end
    else
    if lua_isstring(L,paramstart) then
    begin
      routine:=lua_tostring(L,paramstart);
      //get a reference to this function

      lua_getglobal(L, pchar(routine));
      f:=luaL_ref(L,LUA_REGISTRYINDEX);
    end
    else
      exit;


    c.syncfunction:=f;
    if paramcount>=2 then
    begin
      c.syncparam:=paramstart+1;
      c.syncparamcount:=paramcount-1;
    end
    else
      c.syncparam:=0;

    c.Synchronize(c, c.sync);

    luaL_unref(L, LUA_REGISTRYINDEX, f);

    result:=1;
  end;
end;

function thread_waitfor(L: PLua_State): integer; cdecl;
var
  parameters: integer;
  c: TCEThread;
begin
  result:=0;
  c:=luaclass_getClassObject(L);
  c.WaitFor;
end;

function thread_terminate(L: PLua_State): integer; cdecl;
begin
  TCEThread(luaclass_getClassObject(L)).Terminate;
  result:=0;
end;

function thread_suspend(L: PLua_State): integer; cdecl;
begin
  TCEThread(luaclass_getClassObject(L)).Suspend;
  result:=0;
end;

function thread_resume(L: PLua_State): integer; cdecl;
begin
  TCEThread(luaclass_getClassObject(L)).Resume;
  result:=0;
end;

procedure thread_addMetaData(L: PLua_state; metatable: integer; userdata: integer);
begin
  object_addMetaData(L, metatable, userdata);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'freeOnTerminate', thread_freeOnTerminate);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'synchronize', thread_synchronize);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'waitfor', thread_waitfor);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'terminate', thread_terminate);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'suspend', thread_suspend);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'resume', thread_resume);
end;

function criticalsection_enter(L: PLua_State): integer; cdecl;
begin
  TCriticalSection(luaclass_getClassObject(L)).Enter;
  result:=0;
end;

function criticalsection_leave(L: PLua_State): integer; cdecl;
begin
  TCriticalSection(luaclass_getClassObject(L)).Leave;
  result:=0;
end;

function criticalsection_tryEnter(L: PLua_State): integer; cdecl;
begin
  lua_pushboolean(L, TCriticalSection(luaclass_getClassObject(L)).TryEnter);
  result:=1;
end;

procedure criticalsection_addMetaData(L: PLua_state; metatable: integer; userdata: integer);
begin
  object_addMetaData(L, metatable, userdata);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'enter', criticalsection_enter);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'leave', criticalsection_leave);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'acquire', criticalsection_enter);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'release', criticalsection_leave);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'tryEnter', criticalsection_tryEnter);
end;

function event_resetEvent(L: PLua_State): integer; cdecl;
begin
  TEvent(luaclass_getClassObject(L)).ResetEvent;
  result:=0;
end;

function event_setEvent(L: PLua_State): integer; cdecl;
begin
  TEvent(luaclass_getClassObject(L)).setEvent;
  result:=0;
end;


function event_waitfor(L: PLua_State): integer; cdecl;
begin
  if lua_gettop(L)=0 then exit(0);

  lua_pushinteger(L, integer(TEvent(luaclass_getClassObject(L)).WaitFor(lua_tointeger(L, 1))));
  result:=1;
end;

procedure event_addMetaData(L: PLua_state; metatable: integer; userdata: integer);
begin
  object_addMetaData(L, metatable, userdata);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'resetEvent', event_resetEvent);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'setEvent', event_setEvent);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'waitFor', event_waitFor);
end;


function semaphore_acquire(L: PLua_State): integer; cdecl;
begin
  TSemaphore(luaclass_getClassObject(L)).Acquire;
  result:=0;
end;

function semaphore_release(L: PLua_State): integer; cdecl;
begin
  TSemaphore(luaclass_getClassObject(L)).Release;
  result:=0;
end;

procedure semaphore_addMetaData(L: PLua_state; metatable: integer; userdata: integer);
begin
  object_addMetaData(L, metatable, userdata);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'acquire', semaphore_acquire);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'release', semaphore_release);
end;

function mrew_beginWrite(L: PLua_State): integer; cdecl;
begin
  TMultiReadExclusiveWriteSynchronizer(luaclass_getClassObject(L)).Beginwrite;
  result:=0;
end;

function mrew_endWrite(L: PLua_State): integer; cdecl;
begin
  TMultiReadExclusiveWriteSynchronizer(luaclass_getClassObject(L)).Endwrite;
  result:=0;
end;

function mrew_beginRead(L: PLua_State): integer; cdecl;
begin
  TMultiReadExclusiveWriteSynchronizer(luaclass_getClassObject(L)).BeginRead;
  result:=0;
end;

function mrew_endRead(L: PLua_State): integer; cdecl;
begin
  TMultiReadExclusiveWriteSynchronizer(luaclass_getClassObject(L)).EndRead;
  result:=0;
end;

procedure mrew_addMetaData(L: PLua_state; metatable: integer; userdata: integer);
begin
  object_addMetaData(L, metatable, userdata);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'beginWrite', mrew_beginWrite);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'endWrite', mrew_endWrite);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'beginRead', mrew_beginRead);
  luaclass_addClassFunctionToTable(L, metatable, userdata, 'endRead', mrew_endRead);
end;


procedure initializeLuaThread;
begin
  lua_register(LuaVM, 'createNativeThread', createNativeThread);
  lua_register(LuaVM, 'createNativeThreadSuspended', createNativeThreadSuspended);
  lua_register(LuaVM, 'createThread', createNativeThread);
  lua_register(LuaVM, 'createThreadSuspended', createNativeThreadSuspended);
  lua_register(LuaVM, 'getCPUCount', lua_getCPUCount);
  lua_register(LuaVM, 'thread_freeOnTerminate', thread_freeOnTerminate);
  lua_register(LuaVM, 'thread_synchronize', thread_synchronize);
  lua_register(LuaVM, 'thread_waitfor', thread_waitfor);

  lua_register(LuaVM, 'createCriticalSection', luaCreateCriticalSection);
  lua_register(LuaVM, 'createEvent', luaCreateEvent);
  lua_register(LuaVM, 'createSemaphore', luaCreateSemaphore);
  lua_register(LuaVM, 'createMultiReadExclusiveWriteSynchronizer', luaCreateMultiReadExclusiveWriteSynchronizer );
end;

initialization
  luaclass_register(TThread, thread_addMetaData);
 // luaclass_register(TCEThread, thread_addMetaData);

  luaclass_register(TCriticalSection, criticalsection_addMetaData);
  luaclass_register(TEvent, event_addMetaData);
  luaclass_register(TSemaphore, semaphore_addMetaData);
  luaclass_register(TMultiReadExclusiveWriteSynchronizer, mrew_addMetaData);


end.


