fpc/utils/fppkg/lnet/levents.pp
2016-10-02 12:56:59 +00:00

624 lines
15 KiB
ObjectPascal

{ lNet Events abstration
CopyRight (C) 2006-2008 Ales Katona
This library is Free software; you can rediStribute it and/or modify it
under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 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 Library General Public License
for more details.
You should have received a Copy of the GNU Library General Public License
along with This library; if not, Write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
This license has been modified. See File LICENSE.ADDON for more inFormation.
Should you find these sources without a LICENSE File, please contact
me at ales@chello.sk
}
unit lEvents;
{$mode objfpc}{$H+}
{$inline on}
{$define nochoice} // let's presume we don't have "optimized" eventer
interface
uses
{$ifdef Linux}
{$undef nochoice} // undefine for all "Optimized" targets
Linux, Contnrs, Errors,
{$endif}
{$ifdef BSD}
{$undef nochoice}
BSD, Errors,
{$endif}
{$i sys/osunits.inc}
type
TLHandle = class;
TLEventer = class;
TLHandleEvent = procedure (aHandle: TLHandle) of object;
TLHandleErrorEvent = procedure (aHandle: TLHandle; const msg: string) of object;
TLEventerErrorEvent = procedure (const msg: string; Sender: TLEventer) of object;
{ TLHandle }
TLHandle = class(TObject)
protected
FHandle: THandle;
FEventer: TLEventer; // "queue holder"
FOnRead: TLHandleEvent;
FOnWrite: TLHandleEvent;
FOnError: TLHandleErrorEvent;
FIgnoreWrite: Boolean; // so we can do edge-triggered
FIgnoreRead: Boolean; // so we can do edge-triggered
FIgnoreError: Boolean; // so we can do edge-triggered
FDispose: Boolean; // will free in the after-cycle
FFreeing: Boolean; // used to see if it's in the "to be freed" list
FPrev: TLHandle;
FNext: TLHandle;
FFreeNext: TLHandle;
FInternalData: Pointer;
procedure SetIgnoreError(const aValue: Boolean);
procedure SetIgnoreWrite(const aValue: Boolean);
procedure SetIgnoreRead(const aValue: Boolean);
public
UserData: Pointer;
constructor Create; virtual;
destructor Destroy; override;
procedure Free; virtual; // this is a trick
property Prev: TLHandle read FPrev write FPrev;
property Next: TLHandle read FNext write FNext;
property FreeNext: TLHandle read FFreeNext write FFreeNext;
property IgnoreWrite: Boolean read FIgnoreWrite write SetIgnoreWrite;
property IgnoreRead: Boolean read FIgnoreRead write SetIgnoreRead;
property IgnoreError: Boolean read FIgnoreError write SetIgnoreError;
property OnRead: TLHandleEvent read FOnRead write FOnRead;
property OnWrite: TLHandleEvent read FOnWrite write FOnWrite;
property OnError: TLHandleErrorEvent read FOnError write FOnError;
property Dispose: Boolean read FDispose write FDispose;
property Handle: THandle read FHandle write FHandle;
property Eventer: TLEventer read FEventer;
end;
{ TLTimer }
{
TLTimer = class(TObject)
protected
FOnTimer: TNotifyEvent;
FInterval: TDateTime;
FTimeout: TDateTime;
FPeriodic: Boolean;
FEnabled: Boolean;
FNext: TLTimer;
function GetInterval: Integer;
procedure SetEnabled(NewEnabled: Boolean);
procedure SetInterval(NewInterval: Integer);
public
procedure CallAction;
property Enabled: Boolean read FEnabled write SetEnabled;
property Interval: Integer read GetInterval write SetInterval;
property Periodic: Boolean read FPeriodic write FPeriodic;
property OnTimer: TNotifyEvent read FOnTimer write FOnTimer;
end;
}
{ TLTimeoutManager }
{
TLSetTimeout = procedure(NewTimeout: DWord) of object;
TLTimeoutManager = class
protected
FFirst: TLTimer;
FLast: TLTimer;
FTimeout: DWord;
FSetTimeout: TLSetTimeout;
public
destructor Destroy; override;
procedure AddTimer(ATimer: TLTimer);
procedure RemoveTimer(ATimer: TLTimer);
procedure CallAction;
end;
}
{ TLEventer }
TLEventer = class
protected
FRoot: TLHandle;
FCount: Integer;
FOnError: TLEventerErrorEvent;
FReferences: Integer;
FFreeRoot: TLHandle; // the root of "free" list if any
FFreeIter: TLHandle; // the last of "free" list if any
FInLoop: Boolean;
function GetCount: Integer; virtual;
function GetTimeout: Integer; virtual;
procedure SetTimeout(const Value: Integer); virtual;
function Bail(const msg: string; const Ernum: Integer): Boolean;
procedure AddForFree(aHandle: TLHandle);
procedure FreeHandles;
procedure HandleIgnoreError(aHandle: TLHandle); virtual;
procedure HandleIgnoreWrite(aHandle: TLHandle); virtual;
procedure HandleIgnoreRead(aHandle: TLHandle); virtual;
function GetInternalData(aHandle: TLHandle): Pointer;
procedure SetInternalData(aHandle: TLHandle; const aData: Pointer);
procedure SetHandleEventer(aHandle: TLHandle);
procedure InternalUnplugHandle(aHandle: TLHandle); virtual;
public
constructor Create; virtual;
destructor Destroy; override;
function AddHandle(aHandle: TLHandle): Boolean; virtual;
function CallAction: Boolean; virtual;
procedure RemoveHandle(aHandle: TLHandle); virtual;
procedure UnplugHandle(aHandle: TLHandle);
procedure UnregisterHandle(aHandle: TLHandle); virtual;
procedure LoadFromEventer(aEventer: TLEventer); virtual;
procedure Clear;
procedure AddRef;
procedure DeleteRef;
property Timeout: Integer read GetTimeout write SetTimeout;
property OnError: TLEventerErrorEvent read FOnError write FOnError;
property Count: Integer read GetCount;
end;
TLEventerClass = class of TLEventer;
{ TLSelectEventer }
TLSelectEventer = class(TLEventer)
protected
FTimeout: TTimeVal;
FReadFDSet: TFDSet;
FWriteFDSet: TFDSet;
FErrorFDSet: TFDSet;
function GetTimeout: Integer; override;
procedure SetTimeout(const Value: Integer); override;
procedure ClearSets;
public
constructor Create; override;
function CallAction: Boolean; override;
end;
{$i sys/lkqueueeventerh.inc}
{$i sys/lepolleventerh.inc}
function BestEventerClass: TLEventerClass;
implementation
uses
syncobjs,
lCommon;
var
CS: TCriticalSection;
{ TLHandle }
procedure TLHandle.SetIgnoreError(const aValue: Boolean);
begin
if FIgnoreError <> aValue then begin
FIgnoreError := aValue;
if Assigned(FEventer) then
FEventer.HandleIgnoreError(Self);
end;
end;
procedure TLHandle.SetIgnoreWrite(const aValue: Boolean);
begin
if FIgnoreWrite <> aValue then begin
FIgnoreWrite := aValue;
if Assigned(FEventer) then
FEventer.HandleIgnoreWrite(Self);
end;
end;
procedure TLHandle.SetIgnoreRead(const aValue: Boolean);
begin
if FIgnoreRead <> aValue then begin
FIgnoreRead := aValue;
if Assigned(FEventer) then
FEventer.HandleIgnoreRead(Self);
end;
end;
constructor TLHandle.Create;
begin
FOnRead := nil;
FOnWrite := nil;
FOnError := nil;
UserData := nil;
FEventer := nil;
FPrev := nil;
FNext := nil;
FFreeNext := nil;
FFreeing := False;
FDispose := False;
FIgnoreWrite := False;
FIgnoreRead := False;
FIgnoreError := False;
end;
destructor TLHandle.Destroy;
begin
if Assigned(FEventer) then
FEventer.InternalUnplugHandle(Self);
end;
procedure TLHandle.Free;
begin
CS.Enter;
if Assigned(FEventer) and FEventer.FInLoop then
FEventer.AddForFree(Self)
else
inherited Free;
CS.Leave;
end;
{ TLTimer }
{
function TLTimer.GetInterval: Integer;
begin
Result := Round(FInterval * MSecsPerDay);
end;
procedure TLTimer.SetEnabled(NewEnabled: integer);
begin
FTimeout := Now + Interval;
FEnabled := true;
end;
procedure TLTimer.SetInterval(const aValue: Integer);
begin
FInterval := AValue / MSecsPerDay;
end;
procedure TLTimer.CallAction;
begin
if FEnabled and Assigned(FOnTimer) and (Now - FStarted >= FInterval) then
begin
FOnTimer(Self);
if not FOneShot then
FStarted := Now
else
FEnabled := false;
end;
end;
}
{ TLEventer }
constructor TLEventer.Create;
begin
FRoot := nil;
FFreeRoot := nil;
FFreeIter := nil;
FInLoop := False;
FCount := 0;
FReferences := 1;
end;
destructor TLEventer.Destroy;
begin
Clear;
end;
function TLEventer.GetCount: Integer;
begin
Result := FCount;
end;
function TLEventer.GetTimeout: Integer;
begin
Result := 0;
end;
procedure TLEventer.SetTimeout(const Value: Integer);
begin
end;
function TLEventer.Bail(const msg: string; const Ernum: Integer): Boolean;
begin
Result := False; // always false, substitute for caller's result
if Assigned(FOnError) then
FOnError(msg + LStrError(Ernum), Self);
end;
procedure TLEventer.AddForFree(aHandle: TLHandle);
begin
if not aHandle.FFreeing then begin
aHandle.FFreeing := True;
if not Assigned(FFreeIter) then begin
FFreeIter := aHandle;
FFreeRoot := aHandle;
end else begin
FFreeIter.FreeNext := aHandle;
FFreeIter := aHandle;
end;
end;
end;
procedure TLEventer.FreeHandles;
var
Temp, Temp2: TLHandle;
begin
Temp := FFreeRoot;
while Assigned(Temp) do begin
Temp2 := Temp.FreeNext;
Temp.Free;
Temp := Temp2;
end;
FFreeRoot := nil;
FFreeIter := nil;
end;
procedure TLEventer.HandleIgnoreError(aHandle: TLHandle);
begin
end;
procedure TLEventer.HandleIgnoreWrite(aHandle: TLHandle);
begin
end;
procedure TLEventer.HandleIgnoreRead(aHandle: TLHandle);
begin
end;
function TLEventer.GetInternalData(aHandle: TLHandle): Pointer;
begin
Result := aHandle.FInternalData;
end;
procedure TLEventer.SetInternalData(aHandle: TLHandle; const aData: Pointer);
begin
aHandle.FInternalData := aData;
end;
procedure TLEventer.SetHandleEventer(aHandle: TLHandle);
begin
aHandle.FEventer := Self;
end;
procedure TLEventer.InternalUnplugHandle(aHandle: TLHandle);
begin
if aHandle.FEventer = Self then begin
if aHandle.FEventer.FInLoop then begin
aHandle.FEventer.AddForFree(aHandle);
Exit;
end;
aHandle.FEventer := nil; // avoid recursive AV
if Assigned(aHandle.FPrev) then begin
aHandle.FPrev.FNext := aHandle.FNext;
if Assigned(aHandle.FNext) then
aHandle.FNext.FPrev := aHandle.FPrev;
end else if Assigned(aHandle.FNext) then begin
aHandle.FNext.FPrev := aHandle.FPrev;
if aHandle = FRoot then
FRoot := aHandle.FNext;
end else FRoot := nil;
if FCount > 0 then
Dec(FCount);
end;
end;
function TLEventer.AddHandle(aHandle: TLHandle): Boolean;
begin
Result := False;
if not Assigned(aHandle.FEventer) then begin
if not Assigned(FRoot) then begin
FRoot := aHandle;
end else begin
if Assigned(FRoot.FNext) then begin
FRoot.FNext.FPrev := aHandle;
aHandle.FNext := FRoot.FNext;
end;
FRoot.FNext := aHandle;
aHandle.FPrev := FRoot;
end;
aHandle.FEventer := Self;
Inc(FCount);
Result := True;
end;
end;
function TLEventer.CallAction: Boolean;
begin
Result := True;
// override in ancestor
end;
procedure TLEventer.RemoveHandle(aHandle: TLHandle);
begin
aHandle.Free;
end;
procedure TLEventer.UnplugHandle(aHandle: TLHandle);
begin
CS.Enter;
InternalUnplugHandle(aHandle);
CS.Leave;
end;
procedure TLEventer.UnregisterHandle(aHandle: TLHandle);
begin
// do nothing, specific to win32 LCLEventer crap (windows is shit)
end;
procedure TLEventer.LoadFromEventer(aEventer: TLEventer);
begin
Clear;
FRoot := aEventer.FRoot;
FOnError := aEventer.FOnError;
end;
procedure TLEventer.Clear;
var
Temp1, Temp2: TLHandle;
begin
Temp1 := FRoot;
Temp2 := FRoot;
while Assigned(Temp2) do begin
Temp1 := Temp2;
Temp2 := Temp1.FNext;
Temp1.Free;
end;
FRoot := nil;
end;
procedure TLEventer.AddRef;
begin
Inc(FReferences);
end;
procedure TLEventer.DeleteRef;
begin
if FReferences > 0 then
Dec(FReferences);
if FReferences = 0 then
Free;
end;
{ TLSelectEventer }
constructor TLSelectEventer.Create;
begin
inherited Create;
FTimeout.tv_sec := 0;
FTimeout.tv_usec := 0;
end;
function TLSelectEventer.GetTimeout: Integer;
begin
if FTimeout.tv_sec < 0 then
Result := -1
else
Result := (FTimeout.tv_sec * 1000) + FTimeout.tv_usec;
end;
procedure TLSelectEventer.SetTimeout(const Value: Integer);
begin
if Value >= 0 then begin
FTimeout.tv_sec := Value div 1000;
FTimeout.tv_usec := Value mod 1000;
end else begin
FTimeout.tv_sec := -1;
FTimeout.tv_usec := 0;
end;
end;
procedure TLSelectEventer.ClearSets;
begin
fpFD_ZERO(FReadFDSet);
fpFD_ZERO(FWriteFDSet);
fpFD_ZERO(FErrorFDSet);
end;
function TLSelectEventer.CallAction: Boolean;
var
Temp, Temp2: TLHandle;
n: Integer;
MaxHandle: THandle;
TempTime: TTimeVal;
begin
if FInLoop then
Exit;
if not Assigned(FRoot) then begin
Sleep(FTimeout.tv_sec * 1000 + FTimeout.tv_usec div 1000);
Exit;
end;
FInLoop := True;
Temp := FRoot;
MaxHandle := 0;
ClearSets;
while Assigned(Temp) do begin
if (not Temp.FDispose ) // handle still valid
and ( (not Temp.IgnoreWrite) // check write or
or (not Temp.IgnoreRead ) // check read or
or (not Temp.IgnoreError)) // check for errors
then begin
if not Temp.IgnoreWrite then
fpFD_SET(Temp.FHandle, FWriteFDSet);
if not Temp.IgnoreRead then
fpFD_SET(Temp.FHandle, FReadFDSet);
if not Temp.IgnoreError then
fpFD_SET(Temp.FHandle, FErrorFDSet);
if Temp.FHandle > MaxHandle then
MaxHandle := Temp.FHandle;
end;
Temp2 := Temp;
Temp := Temp.FNext;
if Temp2.FDispose then
Temp2.Free;
end;
TempTime := FTimeout;
if FTimeout.tv_sec >= 0 then
n := fpSelect(MaxHandle + 1, @FReadFDSet, @FWriteFDSet, @FErrorFDSet, @TempTime)
else
n := fpSelect(MaxHandle + 1, @FReadFDSet, @FWriteFDSet, @FErrorFDSet, nil);
if n < 0 then
Bail('Error on select', LSocketError);
Result := n > 0;
if Result then begin
Temp := FRoot;
while Assigned(Temp) do begin
if not Temp.FDispose and (fpFD_ISSET(Temp.FHandle, FWriteFDSet) <> 0) then
if Assigned(Temp.FOnWrite) and not Temp.IgnoreWrite then
Temp.FOnWrite(Temp);
if not Temp.FDispose and (fpFD_ISSET(Temp.FHandle, FReadFDSet) <> 0) then
if Assigned(Temp.FOnRead) and not Temp.IgnoreRead then
Temp.FOnRead(Temp);
if not Temp.FDispose and (fpFD_ISSET(Temp.FHandle, FErrorFDSet) <> 0) then
if Assigned(Temp.FOnError) and not Temp.IgnoreError then
Temp.FOnError(Temp, 'Handle error' + LStrError(LSocketError));
Temp2 := Temp;
Temp := Temp.FNext;
if Temp2.FDispose then
AddForFree(Temp2);
end;
end;
FInLoop := False;
if Assigned(FFreeRoot) then
FreeHandles;
end;
{$i sys/lkqueueeventer.inc}
{$i sys/lepolleventer.inc}
{$ifdef nochoice}
function BestEventerClass: TLEventerClass;
begin
Result := TLSelectEventer;
end;
{$endif}
initialization
CS := TCriticalSection.Create;
finalization
CS.Free;
end.