unit host_ipc;

{$mode ObjFPC}{$H+}

interface

uses
 Classes,
 SysUtils,
 time,
 mqueue,
 LFQueue,
 host_ipc_interface,
 kern_thr,
 sys_event,
 kern_mtx;

type
 PNodeHeader=^TNodeHeader;
 TNodeHeader=packed record
  mtype:DWORD;
  mlen :DWORD;
  mtid :DWORD;
  buf  :record end;
 end;

 PQNode=^TQNode;
 TQNode=packed record
  next_ :PQNode;
  header:TNodeHeader;
  buf   :record end;
 end;

 PNodeIpcSync=^TNodeIpcSync;
 TNodeIpcSync=packed record
  entry:LIST_ENTRY;
  event:PRTLEvent;
  value:Ptruint;
  tid  :DWORD;
 end;

 THostIpcConnect=class(THostIpcInterface)
  protected
   FQueue:TIntrusiveMPSCQueue;
   FWaits:LIST_HEAD;
   FWLock:mtx;
   Fkq   :Pointer;
   procedure   SyncResult(tid:DWORD;value:Ptruint);
   function    NewNodeSync:PNodeIpcSync;
   procedure   FreeNodeSync(node:PNodeIpcSync);
   procedure   TriggerNodeSync(tid:DWORD;value:Ptruint);
   procedure   Pack(mtype,mlen,mtid:DWORD;buf:Pointer);
   function    Recv:PQNode;
   procedure   Flush;
   procedure   RecvResultNode  (node:PQNode);
   procedure   RecvResultDirect(mlen,mtid:DWORD;buf:Pointer);
   function    RecvKevent      (mlen:DWORD;buf:Pointer):Ptruint;
   procedure   UpdateKevent();
   procedure   WakeupKevent(); virtual;
  public
   //
   function    NewSyncKey:Pointer;       override;
   procedure   FreeSyncKey(key:Pointer); override;
   procedure   WaitSyncKey(key:Pointer); override;
   function    GetSyncValue(key:Pointer):Ptruint; override;
   //
   procedure   Send    (mtype,mlen:DWORD;buf,key:Pointer);  override;
   procedure   SendImpl(mtype,mlen,mtid:DWORD;buf:Pointer); virtual;
   procedure   Update  ();                                  override;
   //
   Constructor Create;
   Destructor  Destroy;     override;
   procedure   thread_new;  virtual;
   procedure   thread_free; virtual;
 end;

 THostIpcSimpleKERN=class;

 THostIpcSimpleMGUI=class(THostIpcConnect)
  FDest:THostIpcSimpleKERN;
  procedure SendImpl(mtype,mlen,mtid:DWORD;buf:Pointer); override;
 end;

 THostIpcSimpleKERN=class(THostIpcConnect)
  FDest     :THostIpcSimpleMGUI;
  FEvent    :PRTLEvent;
  FTerminate:Boolean;
  Constructor Create;
  Destructor  Destroy;     override;
  procedure   thread_new;  override;
  procedure   thread_free; override;
  procedure   SendImpl(mtype,mlen,mtid:DWORD;buf:Pointer); override;
  Function    GetCallback(mtype:DWORD):TOnMessage;     override;
  procedure   WakeupKevent(); override;
 end;

//

 TGameProcess=class
  g_ipc  :THostIpcConnect;
  g_proc :THandle;
  g_p_pid:Integer;
  g_fork :Boolean;
  function   is_terminated:Boolean; virtual;
  function   exit_code:DWORD;       virtual;
  procedure  suspend; virtual;
  procedure  resume;  virtual;
  procedure  stop;    virtual;
  Destructor Destroy; override;
 end;

implementation

Constructor THostIpcConnect.Create;
begin
 inherited;
 FQueue.Create;
 LIST_INIT(@FWaits);
 mtx_init(FWLock,'ipc');
end;

Destructor THostIpcConnect.Destroy;
begin
 Flush;
 mtx_destroy(FWLock);
 if (Fkq<>nil) then
 begin
  kqueue_close2(Fkq);
 end;
 inherited;
end;

procedure THostIpcConnect.thread_new;
begin
 //
end;

procedure THostIpcConnect.thread_free;
begin
 //
end;

procedure THostIpcConnect.Pack(mtype,mlen,mtid:DWORD;buf:Pointer);
var
 node:PQNode;
begin
 node:=AllocMem(SizeOf(TQNode)+mlen);
 node^.header.mtype:=mtype;
 node^.header.mlen :=mlen;
 node^.header.mtid :=mtid;
 Move(buf^,node^.buf,mlen);
 //
 FQueue.Push(node);
end;

function THostIpcConnect.Recv:PQNode;
begin
 Result:=nil;
 FQueue.Pop(Result);
end;

procedure THostIpcConnect.Flush;
var
 node:PQNode;
begin
 node:=nil;
 while FQueue.Pop(node) do
 begin
  FreeMem(node);
 end;
end;

procedure THostIpcConnect.RecvResultNode(node:PQNode);
var
 value:Ptruint;
 mlen:DWORD;
begin
 value:=0;

 mlen:=node^.header.mlen;
 if (mlen>SizeOf(Ptruint)) then
 begin
  mlen:=SizeOf(Ptruint);
 end;

 Move(node^.buf,value,mlen);

 TriggerNodeSync(node^.header.mtid,value);
end;

procedure THostIpcConnect.RecvResultDirect(mlen,mtid:DWORD;buf:Pointer);
var
 value:Ptruint;
begin
 value:=0;

 if (mlen>SizeOf(Ptruint)) then
 begin
  mlen:=SizeOf(Ptruint);
 end;

 Move(buf^,value,mlen);

 TriggerNodeSync(mtid,value);
end;

procedure kq_wakeup(data:Pointer); SysV_ABI_CDecl;
begin
 THostIpcConnect(data).WakeupKevent();
end;

function THostIpcConnect.RecvKevent(mlen:DWORD;buf:Pointer):Ptruint;
var
 kev:p_kevent;
 count:Integer;
begin
 kev  :=buf;
 count:=mlen div SizeOf(t_kevent);

 if (Fkq=nil) then
 begin
  Fkq:=kern_kqueue2('[ipc]',@kq_wakeup,Pointer(Self));
 end;

 //changelist
 Result:=kern_kevent2(Fkq,kev,count,nil,0,nil,@count);
end;

procedure THostIpcConnect.UpdateKevent();
var
 kev:array[0..7] of t_kevent;
 t:timespec;
 r:Integer;
begin
 if (Fkq=nil) then Exit;
 t:=Default(timespec);

 repeat

  r:=0;
  kern_kevent2(Fkq,nil,0,@kev,8,@t,@r);

  if (r>0) then
  begin
   if (iKEV_EVENT=0) then iKEV_EVENT:=HashIpcStr('KEV_EVENT');
   SendAsyn(iKEV_EVENT,r*SizeOf(t_kevent),@kev);
  end;

 until (r<>8);
end;

procedure THostIpcConnect.WakeupKevent();
begin
 //
end;

procedure THostIpcConnect.Update();
var
 node :PQNode;
 value:Ptruint;
 OnMsg:TOnMessage;
begin
 if FStop then Exit;

 node:=Recv;

 while (node<>nil) do
 begin
  //

  if (node^.header.mtype=iRESULT) then
  begin
   RecvResultNode(node);
  end else
  begin
   OnMsg:=GetCallback(node^.header.mtype);
   if (OnMsg<>nil) then
   begin
    value:=OnMsg(node^.header.mlen,@node^.buf);
   end else
   begin
    //nop?
    value:=Ptruint(-1);
   end;
   //is sync
   if (node^.header.mtid<>0) then
   begin
    SyncResult(node^.header.mtid,value);
   end;
  end;

  //
  FreeMem(node); //RenderDoc -> ExceptionCode:0xC0000005
  //
  if FStop then Exit;
  //
  node:=Recv;
 end;
end;

//

procedure THostIpcConnect.SyncResult(tid:DWORD;value:Ptruint);
begin
 SendImpl(iRESULT,SizeOf(Ptruint),tid,@value);
end;

//

function THostIpcConnect.NewNodeSync:PNodeIpcSync;
var
 node:PNodeIpcSync;
begin
 node:=AllocMem(SizeOf(TNodeIpcSync));
 node^.event:=RTLEventCreate;
 node^.tid  :=ThreadID;

 RTLEventResetEvent(node^.event);

 mtx_lock(FWLock);
  LIST_INSERT_HEAD(@FWaits,node,@node^.entry);
 mtx_unlock(FWLock);

 Result:=node;
end;

procedure THostIpcConnect.FreeNodeSync(node:PNodeIpcSync);
begin
 mtx_lock(FWLock);
  LIST_REMOVE(node,@node^.entry);
 mtx_unlock(FWLock);

 RTLEventDestroy(node^.event);

 FreeMem(node);
end;

procedure THostIpcConnect.TriggerNodeSync(tid:DWORD;value:Ptruint);
var
 node:PNodeIpcSync;
begin
 mtx_lock(FWLock);
  node:=LIST_FIRST(@FWaits);

  while (node<>nil) do
  begin
   if (node^.tid=tid) then
   begin
    node^.value:=value;

    RTLEventSetEvent(node^.event);

    Break;
   end;

   node:=LIST_NEXT(node,@node^.entry);
  end;

 mtx_unlock(FWLock);
end;

procedure THostIpcConnect.Send(mtype,mlen:DWORD;buf,key:Pointer);
var
 node:PNodeIpcSync absolute key;
begin
 if (key=nil) then
 begin
  SendImpl(mtype,mlen,0,buf);
 end else
 begin
  SendImpl(mtype,mlen,node^.tid,buf);
 end;
end;

//

function THostIpcConnect.NewSyncKey:Pointer;
begin
 Result:=NewNodeSync;
end;

procedure THostIpcConnect.FreeSyncKey(key:Pointer);
var
 node:PNodeIpcSync absolute key;
begin
 if (node<>nil) then
 begin
  FreeNodeSync(node);
 end;
end;

procedure THostIpcConnect.WaitSyncKey(key:Pointer);
var
 node:PNodeIpcSync absolute key;
begin
 if (node<>nil) then
 begin
  RTLEventWaitFor(node^.event);
 end;
end;

function THostIpcConnect.GetSyncValue(key:Pointer):Ptruint;
var
 node:PNodeIpcSync absolute key;
begin
 if (node<>nil) then
 begin
  Result:=node^.value;
 end else
 begin
  Result:=0;
 end;
end;


//

procedure THostIpcConnect.SendImpl(mtype,mlen,mtid:DWORD;buf:Pointer);
begin
 //
end;

procedure THostIpcSimpleMGUI.SendImpl(mtype,mlen,mtid:DWORD;buf:Pointer);
begin
 if (FDest<>nil) then
 begin
  if (mtype=iRESULT) then
  begin
   //Trigger Direct on Simple mode!
   FDest.RecvResultDirect(mlen,mtid,buf);
  end else
  begin
   FDest.Pack(mtype,mlen,mtid,buf);
  end;
  //
  RTLEventSetEvent(FDest.FEvent);
  //
 end;
end;

procedure simple_kern_thread(parameter:pointer); SysV_ABI_CDecl;
var
 ipc:THostIpcSimpleKERN;
begin
 Writeln('[simple_kern_thread]');

 ipc:=THostIpcSimpleKERN(parameter);

 repeat
  if ipc.FQueue.IsEmpty then
  begin
   RTLEventWaitFor(ipc.FEvent);
  end;
  ipc.Update();
 until ipc.FTerminate;

end;

Constructor THostIpcSimpleKERN.Create;
begin
 inherited;
 FEvent:=RTLEventCreate;
end;

Destructor THostIpcSimpleKERN.Destroy;
begin
 thread_free;
 RTLEventDestroy(FEvent);
 inherited;
end;

procedure THostIpcSimpleKERN.thread_new;
begin
 if (Ftd=nil) then
 begin
  kthread_add(@simple_kern_thread,Self,@Ftd,0,'[ipc_pipe]');
 end;
end;

procedure THostIpcSimpleKERN.thread_free;
begin
 if (Ftd<>nil) then
 begin
  FTerminate:=True;
  RTLEventSetEvent(FEvent);
  WaitForThreadTerminate(p_kthread(Ftd)^.td_handle,0);
  thread_dec_ref(Ftd);
  Ftd:=nil;
 end;
end;

Function THostIpcSimpleKERN.GetCallback(mtype:DWORD):TOnMessage;
begin
 if (iKEV_CHANGE=0) then iKEV_CHANGE:=HashIpcStr('KEV_CHANGE');
 if (mtype=iKEV_CHANGE) then
 begin
  Result:=@RecvKevent;
 end else
 begin
  Result:=inherited;
 end;
end;

procedure THostIpcSimpleKERN.SendImpl(mtype,mlen,mtid:DWORD;buf:Pointer);
begin
 if (FDest<>nil) then
 begin
  if (mtype=iRESULT) then
  begin
   //Trigger Direct on Simple mode!
   FDest.RecvResultDirect(mlen,mtid,buf);
  end else
  begin
   FDest.Pack(mtype,mlen,mtid,buf);
  end;
  //
  if Assigned(Classes.WakeMainThread) then
  begin
   Classes.WakeMainThread(nil);
  end;
  //
 end;
end;

procedure THostIpcSimpleKERN.WakeupKevent();
begin
 UpdateKevent();
end;

//

function TGameProcess.is_terminated:Boolean;
begin
 Result:=False;
end;

function TGameProcess.exit_code:DWORD;
begin
 Result:=0;
end;

procedure TGameProcess.suspend;
begin
 //
end;

procedure TGameProcess.resume;
begin
 //
end;

procedure TGameProcess.stop;
begin
 //
end;

Destructor TGameProcess.Destroy;
begin
 FreeAndNil(g_ipc);
 inherited;
end;


end.

