mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-21 22:29:24 +02:00
624 lines
15 KiB
ObjectPascal
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.
|