FPPS4/rtl/evbuffer.pas

572 lines
12 KiB
Plaintext

{ IO event buffer
Copyright (C) 2018-2023 Red_prig
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 with the following modification:
As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent modules,and
to copy and distribute the resulting executable under terms of your choice,
provided that you also meet, for each linked independent module, the terms
and conditions of the license of that module. An independent module is a
module which is not derived from or based on this library. If you modify
this library, you may extend this exception to your version of the library,
but you are not obligated to do so. If you do not wish to do so, delete this
exception statement from your 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.
}
unit evbuffer;
{$mode ObjFPC}{$H+}
interface
uses
atomic;
type
TfuncFree=Function(p:pointer):SizeUInt; register;
Peviovec=^Teviovec;
Teviovec=object
private
next_:Peviovec;
public
base:Pointer;
len :SizeUInt;
pos :SizeUInt;
buf_free:TfuncFree;
vec_free:TfuncFree;
end;
Pevbuffer=^Tevbuffer;
Tevbuffer=object
private
Var
len :SizeUInt;
tail_,head_:Peviovec;
stub_:Pointer;
public
end;
Procedure eviovec_free(P:Peviovec);
function eviovec_next(buf:Pevbuffer;vec:Peviovec):Peviovec;
function eviovec_getdata(vec:Peviovec):Pointer;
function eviovec_getlen(vec:Peviovec):SizeUInt;
procedure evbuffer_init(buf:Pevbuffer);
function evbuffer_new:Pevbuffer;
procedure evbuffer_free(buf:Pevbuffer);
procedure evbuffer_clear(buf:Pevbuffer);
Function evbuffer_isempty(buf:Pevbuffer):Boolean;
function evbuffer_push(buf:Pevbuffer;Node:Peviovec):Boolean;
function evbuffer_pop(buf:Pevbuffer):Peviovec;
function evbuffer_peek(buf:Pevbuffer):Peviovec;
function evbuffer_add_ref(buf:Pevbuffer;data:pointer;datapos,datalen:SizeUInt;ff:TfuncFree):Boolean;
function evbuffer_remove_ref(buf:Pevbuffer;var data:pointer;var datapos,datalen:SizeUInt;var ff:TfuncFree):Boolean;
function evbuffer_add(buf:Pevbuffer;data:pointer;datalen:SizeUInt):Boolean;
function evbuffer_remove(buf:Pevbuffer;data:pointer;datalen:SizeUInt):SizeUInt;
function evbuffer_copy(buf:Pevbuffer;data:pointer;datalen:SizeUInt):SizeUInt;
function evbuffer_drain(buf:Pevbuffer;datalen:SizeUInt):SizeUInt;
function evbuffer_get_length(buf:Pevbuffer):SizeUInt;
function evbuffer_get_contiguous_space(buf:Pevbuffer):SizeUInt;
function evbuffer_get_atmost_size(buf:Pevbuffer;size:SizeUint):SizeUInt;
function evbuffer_get_atless_size(buf:Pevbuffer;size:SizeUint):SizeUInt;
function evbuffer_move(Src,Dst:Pevbuffer):SizeUInt;
function evbuffer_move_length(Src,Dst:Pevbuffer;length:SizeUInt):SizeUInt;
function Freemem_ptr:TfuncFree;
implementation
function Freemem_ptr:TfuncFree;
Var
MemMgr:TMemoryManager;
begin
MemMgr:=Default(TMemoryManager);
GetMemoryManager(MemMgr);
Result:=MemMgr.Freemem;
end;
//
Procedure eviovec_free(P:Peviovec);
begin
if not Assigned(P) then Exit;
if Assigned(P^.buf_free) then
begin
P^.buf_free(P^.base);
end;
if Assigned(P^.vec_free) then
begin
P^.vec_free(P);
end;
end;
function eviovec_next(buf:Pevbuffer;vec:Peviovec):Peviovec;
Var
tail,n:Peviovec;
begin
Result:=nil;
if (not Assigned(buf)) or
(not Assigned(vec)) then Exit;
With vec^ do
begin
tail:=vec^.next_;
if not Assigned(tail) then Exit;
n:=load_consume(tail^.next_);
if tail=@buf^.stub_ then
begin
if n=nil then Exit;
tail:=n;
end;
Result:=tail;
end;
end;
function eviovec_getdata(vec:Peviovec):Pointer;
begin
Result:=nil;
if not Assigned(vec) then Exit;
Result:=@PByte(vec^.base)[vec^.pos];
end;
function eviovec_getlen(vec:Peviovec):SizeUInt;
begin
Result:=0;
if not Assigned(vec) then Exit;
Result:=vec^.len;
end;
//--evbuffer--
procedure evbuffer_init(buf:Pevbuffer);
begin
if not Assigned(buf) then Exit;
buf^:=Default(Tevbuffer);
With buf^ do
begin
head_:=Peviovec(@stub_);
tail_:=Peviovec(@stub_);
end;
ReadWriteBarrier;
end;
function evbuffer_new:Pevbuffer;
begin
Result:=GetMem(SizeOf(Tevbuffer));
evbuffer_init(Result);
end;
procedure evbuffer_free(buf:Pevbuffer);
begin
if not Assigned(buf) then Exit;
evbuffer_clear(buf);
FreeMem(buf);
end;
procedure evbuffer_clear(buf:Pevbuffer);
Var
Node:Peviovec;
begin
if not Assigned(buf) then Exit;
repeat
Node:=evbuffer_pop(buf);
eviovec_free(Node);
until (Node=nil);
end;
Function evbuffer_isempty(buf:Pevbuffer):Boolean;
begin
if not Assigned(buf) then Exit(true);
Result:=(load_acquire(buf^.head_)=@buf^.stub_);
end;
function evbuffer_push(buf:Pevbuffer;Node:Peviovec):Boolean;
Var
prev:Peviovec;
begin
if (not Assigned(buf)) or (not Assigned(Node)) then Exit(False);
With buf^ do
begin
store_release(Node^.next_,nil);
prev:=XCHG(head_,Node);
store_release(prev^.next_,Node);
fetch_add(len,Node^.len);
end;
Result:=True;
end;
function evbuffer_pop(buf:Pevbuffer):Peviovec;
Var
tail,n,head:Peviovec;
begin
Result:=nil;
if not Assigned(buf) then Exit;
With buf^ do
begin
tail:=tail_;
n:=load_consume(tail^.next_);
if tail=@stub_ then
begin
if n=nil then Exit;
store_release(tail_,n);
tail:=n;
n:=load_consume(n^.next_);
end;
if n<>nil then
begin
store_release(tail_,n);
Result:=tail;
store_release(tail^.next_,nil);
fetch_sub(len,Result^.len);
Exit;
end;
head:=head_;
if tail<>head then Exit;
stub_:=nil;
n:=XCHG(head_,@stub_);
store_release(n^.next_,@stub_);
n:=load_consume(tail^.next_);
if n<>nil then
begin
store_release(tail_,n);
Result:=tail;
store_release(tail^.next_,nil);
fetch_sub(len,Result^.len);
Exit;
end;
end;
end;
function evbuffer_peek(buf:Pevbuffer):Peviovec;
Var
tail,n:Peviovec;
begin
Result:=nil;
if not Assigned(buf) then Exit;
With buf^ do
begin
tail:=tail_;
if not Assigned(tail) then Exit;
n:=load_consume(tail^.next_);
if tail=@stub_ then
begin
if not Assigned(n) then Exit;
tail:=n;
end;
Result:=tail;
end;
end;
function evbuffer_get_atmost_size(buf:Pevbuffer;size:SizeUint):SizeUInt;
Var
vec:Peviovec;
begin
Result:=0;
if size=0 then Exit;
vec:=evbuffer_peek(buf);
if not Assigned(vec) then Exit;
Result:=vec^.len;
if Result>=size then
begin
Result:=size;
end else
begin
repeat
vec:=eviovec_next(buf,vec);
if not Assigned(vec) then Break;
if Result+vec^.len>size then Break;
Result:=Result+vec^.len;
until false;
end;
end;
function evbuffer_get_atless_size(buf:Pevbuffer;size:SizeUint):SizeUInt;
Var
vec:Peviovec;
begin
Result:=0;
if size=0 then Exit;
vec:=evbuffer_peek(buf);
if not Assigned(vec) then Exit;
Result:=vec^.len;
if Result<size then
begin
repeat
vec:=eviovec_next(buf,vec);
if not Assigned(vec) then Break;
Result:=Result+vec^.len;
if Result>=size then Break;
until false;
end;
end;
function evbuffer_move(Src,Dst:Pevbuffer):SizeUInt;
Var
vec:Peviovec;
begin
Result:=0;
if Assigned(Dst) then
repeat
vec:=evbuffer_pop(Src);
if vec=nil then Exit;
Result:=Result+vec^.len;
evbuffer_push(Dst,vec);
until false;
end;
function evbuffer_move_length(Src,Dst:Pevbuffer;length:SizeUInt):SizeUInt;
var
i:SizeUInt;
vec:Peviovec;
begin
Result:=0;
if Assigned(Dst) then
repeat
vec:=evbuffer_peek(Src);
if vec=nil then Exit;
i:=Result+vec^.len;
if i>length then
begin
i:=length-Result;
evbuffer_add(Dst,eviovec_getdata(vec),i);
evbuffer_drain(Src,i);
Result:=length;
Exit;
end else
begin
evbuffer_push(Dst,evbuffer_pop(Src));
Result:=i;
if length=Result then Exit;
end;
until false;
end;
Var
cache_peviovec:Peviovec=nil;
Function get_peviovec:peviovec;
begin
Result:=XCHG(cache_peviovec,nil);
if Result=nil then
begin
Result:=GetMem(SizeOf(Teviovec));
end;
end;
Function free_peviovec(p:pointer):SizeUInt;
begin
Result:=FreeMem(XCHG(cache_peviovec,p));
end;
function evbuffer_add_ref(buf:Pevbuffer;data:pointer;datapos,datalen:SizeUInt;ff:TfuncFree):Boolean;
Var
Node:Peviovec;
begin
Result:=False;
if (not Assigned(buf)) or
(not Assigned(data)) or
(datalen=0) then Exit;
//Node:=GetMem(SizeOf(Teviovec));
Node:=get_peviovec;
if Node=nil then Exit;
With Node^ do
begin
base:=data;
len:=datalen;
pos:=datapos;
buf_free:=ff;
//vec_free:=Freemem_ptr;
vec_free:=@free_peviovec;
end;
Result:=evbuffer_push(buf,Node);
end;
function evbuffer_remove_ref(buf:Pevbuffer;var data:pointer;var datapos,datalen:SizeUInt;var ff:TfuncFree):Boolean;
Var
Node:Peviovec;
begin
Node:=evbuffer_pop(buf);
Result:=Assigned(Node);
if Result then
begin
data :=Node^.base;
datapos:=Node^.pos;
datalen:=Node^.len;
ff :=Node^.buf_free;
if Assigned(Node^.vec_free) then
begin
Node^.vec_free(Node);
end;
end;
end;
function _evbuffer_add_opt(buf:Pevbuffer;data:pointer;datalen:SizeUInt):Boolean;
Var
Node:Peviovec;
begin
Result:=False;
Node:=GetMem(datalen+SizeOf(Teviovec));
if Node=nil then Exit;
With Node^ do
begin
base:=@PByte(Node)[SizeOf(Teviovec)];
len:=datalen;
pos:=0;
buf_free:=nil;
vec_free:=Freemem_ptr;
end;
Move(data^,Node^.base^,datalen);
Result:=evbuffer_push(buf,Node);
end;
function evbuffer_add(buf:Pevbuffer;data:pointer;datalen:SizeUInt):Boolean;
Const
optimal_size=4*1024-SizeOf(Teviovec)-2*SizeOf(Pointer);
Var
base:Pointer;
begin
Result:=False;
if (not Assigned(buf)) or
(not Assigned(data)) or
(datalen=0) then Exit;
if (datalen<=optimal_size) then
begin
Result:=_evbuffer_add_opt(buf,data,datalen);
end else
begin
base:=GetMem(datalen);
Move(data^,base^,datalen);
Result:=evbuffer_add_ref(buf,base,0,datalen,Freemem_ptr);
end;
end;
function evbuffer_remove(buf:Pevbuffer;data:pointer;datalen:SizeUInt):SizeUInt;
Var
vec:Peviovec;
begin
Result:=0;
if not Assigned(data) then Exit;
While (datalen<>0) do
begin
vec:=evbuffer_peek(buf);
if not Assigned(vec) then Break;
With vec^ do
begin
if (len>datalen) then
begin
Move(PByte(base)[pos],data^,datalen);
pos:=pos+datalen;
len:=len-datalen;
Result:=Result+datalen;
fetch_sub(buf^.len,datalen);
Break;
end else
begin
Move(PByte(base)[pos],data^,len);
datalen:=datalen-len;
Result:=Result+len;
data:=@PByte(data)[len];
eviovec_free(evbuffer_pop(buf));
end;
end;
end;
end;
function evbuffer_copy(buf:Pevbuffer;data:pointer;datalen:SizeUInt):SizeUInt;
Var
vec:Peviovec;
begin
Result:=0;
if not Assigned(data) then Exit;
vec:=evbuffer_peek(buf);
While (datalen<>0) and Assigned(vec) do
begin
With vec^ do
begin
if (len>datalen) then
begin
Move(PByte(base)[pos],data^,datalen);
Result:=Result+datalen;
Break;
end else
begin
Move(PByte(base)[pos],data^,len);
datalen:=datalen-len;
Result:=Result+len;
data:=@PByte(data)[len];
vec:=eviovec_next(buf,vec);
end;
end;
end;
end;
function evbuffer_drain(buf:Pevbuffer;datalen:SizeUInt):SizeUInt;
Var
vec:Peviovec;
begin
Result:=0;
While (datalen<>0) do
begin
vec:=evbuffer_peek(buf);
if not Assigned(vec) then Break;
With vec^ do
begin
if (len>datalen) then
begin
pos:=pos+datalen;
len:=len-datalen;
Result:=Result+datalen;
fetch_sub(buf^.len,datalen);
Break;
end else
begin
datalen:=datalen-len;
Result:=Result+len;
eviovec_free(evbuffer_pop(buf));
end;
end;
end;
end;
function evbuffer_get_length(buf:Pevbuffer):SizeUInt;
begin
Result:=0;
if not Assigned(buf) then Exit;
Result:=buf^.len;
end;
function evbuffer_get_contiguous_space(buf:Pevbuffer):SizeUInt;
Var
vec:Peviovec;
begin
Result:=0;
vec:=evbuffer_peek(buf);
if Assigned(vec) then
begin
Result:=vec^.len;
end;
end;
end.