Having problem storing an array in a TQueue. Any idea where I go wrong? Code works fine in Delphi XE 5 but not in Delphi 10 Seattle.
(I can't decide if this is a bug or how it should work. Tried searching embarcadero for clues but failed.)
procedure TForm1.Button1Click(Sender: TObject);
var
FData: TQueue<TBytes>;
FsData: TQueue<String>;
arr: TBytes;
begin
FData := TQueue<TBytes>.Create;
FsData := TQueue<String>.Create;
try
setlength(arr, 3);
arr[0] := 1;
arr[1] := 2;
arr[2] := 3;
FData.Enqueue(arr);
Memo1.Lines.Add('Count, array:' + IntToStr(FData.Count)); // 0?
FsData.Enqueue('asada');
Memo1.Lines.Add('Count, string:' + IntToStr(FsData.Count)); // 1
finally
FData.Free;
FsData.Free;
end;
end;
A queue data structure can be implemented using one dimensional array. The queue implemented using array stores only fixed number of data values. The implementation of queue data structure using array is very simple.
You can define an array of arrays in Java. Outer array contains arrays elements.
We can create such a memory representation by using an array of arrays. There are two fundamental ways by which to create two dimensional arrays in LiveCode: 1. First create a number of arrays that each stores one dimensional data, then store these arrays in a new array to create the second dimension.
In queue, insertion and deletion happen at the opposite ends, so implementation is not as simple as stack . To implement a queue using array, create an array arr of size n and take two variables front and rear both of which will be initialized to 0 which means the queue is currently empty.
Below is the tabular representation of the difference between Array, Stack, and Queue: Queues are based on the FIFO principle, i.e., the element inserted at the first, is the first element to come out of the list. Stacks are based on the LIFO principle, i.e., the element inserted at the last, is the first element to come out of the list.
So please help me out how to store multiple values in a Single Queue Item? You can use the “Collections” property when adding a queue item using “Add Queue Item” activity, and the SpecificContent property of the QueueItem object when processing the Queue Item.
You can use the “Collections” property when adding a queue item using “Add Queue Item” activity, and the SpecificContent property of the QueueItem object when processing the Queue Item.
This is a defect introduced in XE8. Here's the simplest reproduction that I can produce.
{$APPTYPE CONSOLE}
uses
System.Generics.Collections;
var
Queue: TQueue<TArray<Byte>>;
begin
Queue := TQueue<TArray<Byte>>.Create;
Queue.Enqueue(nil);
Writeln(Queue.Count);
end.
The output is 1 in XE7 and 0 in XE8 and Seattle.
This has already been reported to Embarcadero: RSP-13196.
The implementation of Enqueue
looks like this:
procedure TQueue<T>.Enqueue(const Value: T);
begin
if IsManagedType(T) then
if (SizeOf(T) = SizeOf(Pointer)) and (GetTypeKind(T) <> tkRecord) then
FQueueHelper.InternalEnqueueMRef(Value, GetTypeKind(T))
else
FQueueHelper.InternalEnqueueManaged(Value)
else
case SizeOf(T) of
1: FQueueHelper.InternalEnqueue1(Value);
2: FQueueHelper.InternalEnqueue2(Value);
4: FQueueHelper.InternalEnqueue4(Value);
8: FQueueHelper.InternalEnqueue8(Value);
else
FQueueHelper.InternalEnqueueN(Value);
end;
end;
When T
is a dynamic array, the FQueueHelper.InternalEnqueueMRef
branch is chosen. This in turn looks like this:
procedure TQueueHelper.InternalEnqueueMRef(const Value; Kind: TTypeKind);
begin
case Kind of
TTypeKind.tkUString: InternalEnqueueString(Value);
TTypeKind.tkInterface: InternalEnqueueInterface(Value);
{$IF not Defined(NEXTGEN)}
TTypeKind.tkLString: InternalEnqueueAnsiString(Value);
TTypeKind.tkWString: InternalEnqueueWideString(Value);
{$ENDIF}
{$IF Defined(AUTOREFCOUNT)}
TTypeKind.tkClass: InternalEnqueueObject(Value);
{$ENDIF}
end;
end;
Note that there is no entry for TTypeKind.tkDynArray
. Because these two methods are inlined, the inliner manages to compress it all down to nothing. No action is performed when you Enqueue
a dynamic array.
Back in the good old days of XE7 the code looked like this:
procedure TQueue<T>.Enqueue(const Value: T);
begin
if Count = Length(FItems) then
Grow;
FItems[FHead] := Value;
FHead := (FHead + 1) mod Length(FItems);
Inc(FCount);
Notify(Value, cnAdded);
end;
No scope for type specific defects there.
I don't think that there's an easy workaround for you. Perhaps the most expedient way to proceed is to take the code for the XE7 TQueue
and use that in place of the broken implementation from XE8 and Seattle. For the record, I've given up on the Embarcadero generic collections and use my own classes.
The back story here is that in XE8, Embarcadero decided to address a deficiency in their implementation of generics. Whenever you instantiate a generic type, copies of all the methods are created. For some methods, identical code is generated for different instantiations.
So it is quite common for TGeneric<TFoo>.DoSomething
and TGeneric<TBar>.DoSomething
to have identical code. Other compilers for other languages, C++ templates, .net generics, etc., recognise this duplication and merge together identical generic methods. The Delphi compiler does not. The end result is a larger executable than strictly necessary.
In XE8 Embarcadero decided to tackle this in what I regard was utterly the wrong way. Instead of attacking the root cause of the issue, the compiler, they decided to change the implementation of their generic collection classes. If you look at the code in Generics.Collections
, you will see that it has been completely re-written in XE8. Where previously the code from XE7 and earlier was readable, from XE8 it is now exceedingly complex and opaque. This decision had the following consequences:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With