Jump to content

Primož Gabrijelčič

Members
  • Content Count

    247
  • Joined

  • Last visited

  • Days Won

    11

Everything posted by Primož Gabrijelčič

  1. Primož Gabrijelčič

    Hands-On Design Patterns with Delphi

    A convenient timespace coordinate to get a copy of my book signed (or to buy a fresh new one 😉 ) Bergamo on 17th and 18th of April (https://conf.spring4d.com).
  2. Primož Gabrijelčič

    Dragging a control with $F012

    Some additional info can be found here: https://stackoverflow.com/questions/3976610/moving-a-caption-less-window-by-using-a-drag-area.
  3. Primož Gabrijelčič

    How to pass a parameter to a certain stage of the pipeline?

    Forgot to write that? Oh, me! 😞 In this case, the code can just assume that the first value in the pipeline is a database name: function Build_DbInserterStage(const ADatabaseName: string): TPipelineStageDelegateEx; begin Result := ( procedure(const input, output: IOmniBlockingCollection; const task: IOmniTask) var ovIN: TOmniValue; DB: TxxxDatabase; begin DB := TxxxDatabase.Create(); DB.DatabaseName := ovIN.Take; for ovIN in input do begin // ... insert downloaded file end; DB.Commit; end); end; In more general terms you can declare your special messages which carry metadata (database name) instead of a normal data (to be operated upon). The worker thread can then check the type of the message and react accordingly. You can use TOmniValue's array support for that: pipeline.PipelineStage[3].Input.Add(TOmniValue.CreateNamed(['Type', 'Config', 'DBName', dbName])); pipeline.Input.Add(TOmniValue.CreateNamed(['Type', 'Data', 'Value', value])); for ov in input do if ov['Type'] = 'Config' then db.DatabaseName := ov['DBName'] else Process(ov['Value']);
  4. Primož Gabrijelčič

    How to pass a parameter to a certain stage of the pipeline?

    IOmniPipeline.PipelineStage[num] returns an interface which exposes Input and Output pipeline for that stage: type IOmniPipelineStage = interface ['{DFDA7A07-6B28-4AA6-9218-59D3DF9C4B8E}'] function GetInput: IOmniBlockingCollection; function GetOutput: IOmniBlockingCollection; // property Input: IOmniBlockingCollection read GetInput; property Output: IOmniBlockingCollection read GetOutput; end; IOmniPipeline = interface function GetPipelineStage(idxStage: integer): IOmniPipelineStage; // ... property PipelineStage[idxStage: integer]: IOmniPipelineStage read GetPipelineStage; end; You can use them to send data to a specific pipeline stage, for example: pipeline.PipelineStage[2].Input.Add(42); First pipeline stage has index 0.
  5. Primož Gabrijelčič

    Omni for Multi Platform?

    Sure, when FPC supports all necessary language constructs.
  6. Primož Gabrijelčič

    'Execution queue' options?

    OmniThreadLibrary has many similar constructs. The closes of them is possibly a Pipeline, or a BackgroundWorker, or an Async, depending of what exactly do you want to do with the code. There's a separate subforum for OmniThreadLibrary here: https://en.delphipraxis.net/forum/32-omnithreadlibrary/
  7. Primož Gabrijelčič

    OmniThreadLibrary 3.07.7

    New OmniThreadLibrary is out! Get it while it’s hot! Version 3.07.7 is mostly a bugfix release. It fixes a stupid mistake introduced in version 3.07.6 plus some other minor bugs. You can get it now on git, download the ZIP archive, install it with Delphinus or with GetIt. For more information, visit OmniThreadLibrary home page or write your question on the forum. New features [HHasenack] On XE3 and above, TOmniValue.CastTo<T> supports casting to an interface. [HHasenack] Implemented Parallel.ForEach(IEnumerator<T>) and Parallel.ForEach(IEnumerable<T>). Bug fixes If additional wait objects registered with RegisterWaitObject were constantly signalled, timers were never called. OtlParallel threads were incorrectly scheduled to the main pool instead of GlobalParallelPool unless IOmniTaskConfig.ThreadPool was used. (introduced in 3.07.6) Using Parallel.Join, .For, and .ForEach with .OnStopInvoke failed with access violation if Join/For/ForEach was not executed with .NoWait. Thread pool creation code waits for thread pool management thread to be started and initialized. Without that, CountQueued and CountExecuting may not be initialized correctly when thread pool creation code exits. [tnx to Roland Skinner]
  8. Primož Gabrijelčič

    Caching with class variables

    Original post: https://www.thedelphigeek.com/2019/01/caching-with-class-variables.html Recently I was extending a swiss-army-knife helper record we are using at work and I noticed a small piece of suboptimal code. At first I let it be as I couldn’t think of a simple way of improving the code – and the problem was really not so big that it should be fixed with a complicated solution. At some point, however, a simple and elegant solution appeared to me and I like it so much that I want to share it with the world 😉 Instead of showing our helper record in full, I have extracted just a small part of functionality, enough to do a simple demo. The Check method takes an integer, makes sure that it can be cast into an enumerated type or set T and returns the cast type: type Range<T> = record private class function MaxIntVal: Integer; static; inline; class function MinIntVal: Integer; static; inline; public class function Check(const value: Integer): T; static; end; class function Range<T>.Check(const value: Integer): T; begin if (value < MinIntVal) or (value > MaxIntVal) then raise Exception.CreateFmt( 'Value %d lies outside allowed range for %s (%d .. %d)', [value, PTypeInfo(TypeInfo(T)).Name, MinIntVal, MaxIntVal]); Move(value, Result, SizeOf(Result)); end; Calling Range<TEnum>(i) works the same as executing TEnum(i) with an added bonus of checking for under- and overflows. The following code fragment shows how this function could be used: type TEnum = (en1, en2, en3); TEnumSet = set of TEnum; var en: TEnum; ens: TEnumSet; en := Range<TEnum>.Check(2); // OK, en = en3 en := Range<TEnum>.Check(3); // exception ens := Range<TEnumSet>.Check(0); // OK, ens = [] ens := Range<TEnumSet>.Check(8); // exception The Check function uses following two helper functions to determine lowest and highest possible value for type T: class function Range<T>.MaxIntVal: Integer; var ti: PTypeInfo; typeData: PTypeData; isSet: Boolean; i: Integer; begin ti := TypeInfo(T); isSet := ti.Kind = tkSet; if isSet then ti := GetTypeData(ti).CompType^; typeData := GetTypeData(ti); if isSet then begin Result := 0; for i := typeData.MinValue to typeData.MaxValue do Result := Result or (1 shl i); end else Result := typeData.MaxValue; end; class function Range<T>.MinIntVal: Integer; var ti: PTypeInfo; typeData: PTypeData; begin ti := TypeInfo(T); if ti.Kind = tkSet then ti := GetTypeData(ti).CompType^; typeData := GetTypeData(ti); Result:= typeData.MinValue; end; The suboptimal behaviour comes from the fact that MinIntVal and MaxIntVal are calculated each time Check is called. As type T doesn’t change while the program is being executed, it would suffice to call these two functions once and cache the result. The problem with this solution, however, is twofold. Firstly, this cache would have to exist somewhere. Some part of code would have to manage it. Secondly, it would have to be quite fast. MinIntVal and MaxIntVal, as implemented now, are not very slow and looking up that data in a cache could easily be slower than the current code. As it turns out, we can fix both problems simply by using class variables, properties, and methods functionality of the Delphi language: type TypeInfoCache<T> = class class var FMinIntVal: Integer; FMaxIntVal: Integer; public class constructor Create; class property MaxIntVal: Integer read FMaxIntVal; class property MinIntVal: Integer read FMinIntVal; end; class constructor TypeInfoCache<T>.Create; var ti: PTypeInfo; typeData: PTypeData; isSet: Boolean; i: Integer; begin ti := TypeInfo(T); isSet := ti.Kind = tkSet; if isSet then ti := GetTypeData(ti).CompType^; typeData := GetTypeData(ti); FMinIntVal := typeData.MinValue; if isSet then begin FMaxIntVal := 0; for i := typeData.MinValue to typeData.MaxValue do FMaxIntVal := FMaxIntVal or (1 shl i); end else FMaxIntVal := typeData.MaxValue; end; A class constructor is called only once for each type T used in the code. It is also called automatically and we don’t have to take care of that. Moving the code that calculates min/max values for a type T into a class constructor therefore solves the first problem. To make sure that class part of the TypeInfoCache<T> was created, we merely have to access it, nothing more. The code in Range<T> can be replaced with simple one-liners: class function Range<T>.MaxIntVal: Integer begin Result := TypeInfoCache<T>.MaxIntVal; end; class function Range<T>.MinIntVal: Integer; begin Result := TypeInfoCache<T>.MinIntVal; end; This also solves the second problem, as the access to a class variables doesn’t require any complications usually associated with a dictionary access. Accessing MinIntVal, for example, is a simple call into method that executes few mov instructions. A demonstration project for this new improved solution is available here. This approach is very limited in use – it can only be used to associate data with a type T – but neatly illustrates the power of the Delphi language.
  9. Primož Gabrijelčič

    Caching with class variables

    Class constructors are called when units are initialized, strictly in a single-threaded context.
  10. Primož Gabrijelčič

    Delphi 5 FOR Loop problem (W10-64bit)

    It uses "banker's rounding", after all. 😉
  11. Primož Gabrijelčič

    Delphi 5 FOR Loop problem (W10-64bit)

    http://docwiki.embarcadero.com/RADStudio/en/Declarations_and_Statements_(Delphi)#For_Statements
  12. Primož Gabrijelčič

    Delphi 5 FOR Loop problem (W10-64bit)

    That behavior is correct. `for` loop will execute once with `i` being set to 0.
  13. Primož Gabrijelčič

    ANN: Parnassus Bookmarks and Navigator will be included in the next release of RAD Studio

    Uwe's post clearly states: HKEY_CURRENT_USER\Software\Parnassus OU\Core
  14. Primož Gabrijelčič

    ANN: Parnassus Bookmarks and Navigator will be included in the next release of RAD Studio

    Works like a charm! Thank you!
  15. Primož Gabrijelčič

    ANN: Parnassus Bookmarks and Navigator will be included in the next release of RAD Studio

    Great idea, but unfortunately it doesnt work. Setting it to "($BDS)\Experts" fails in the same way.
  16. Primož Gabrijelčič

    ANN: Parnassus Bookmarks and Navigator will be included in the next release of RAD Studio

    @David Millington Can you - or Embarcadero - please publish Bookmarks and Navigator 1.6 for older platforms? Or create a version that does not depend on share "Core" DLL? I think the community would very much appreciate it. I know I would.
  17. Primož Gabrijelčič

    ANN: Parnassus Bookmarks and Navigator will be included in the next release of RAD Studio

    I have C:\Users\gabr\Documents\Embarcadero\Studio\20.0\CatalogRepository\ParnassusCoreEditor-1.0\ParnassusCoreEditor.dll (modified 15.2.2019) and C:\Users\gabr\AppData\Roaming\Parnassus OU\Common\ParnassusCoreEditor_XBerlin.dll (modified 20.5.2016) So I don't see why the 1.6 install in Rio would break Berlin at all 😞
  18. Primož Gabrijelčič

    ANN: Parnassus Bookmarks and Navigator will be included in the next release of RAD Studio

    Installed Bookmarks & Navigator in Rio. Now my Berlin reports "Access violation at address 07E8D047 in module 'ParnassusCoreEditor.dll'. Read of address 00000000." when I start the IDE. I get the same error when loading a project and then Bookmarks doesn't work. I also get a bunch of errors when I open a form unit and then I get into "Access violation at address 20837CFB in module 'coreide240.bpl'. Read of address 00000018." which repeats indefinitely. 😠 I have Bookmarks 1.5.1 installed in Berlin. Is there anything I can do to have working Bookmarks both in Berlin and Rio?
  19. Primož Gabrijelčič

    Omni for Multi Platform?

    Indeed, I'm working on it. Now that I have finished my book, OTL will again become a top priority side project. It's hard to tell how much work is yet to be done as at the moment some very basic parts are still missing and I don't know how much time I'll need for them. A crowdfund would be nice - one where you can give me additional time inside a day. 30h per day would be good to have 😉
  20. Primož Gabrijelčič

    Generating one-to-many on the fly with Live Bindings

    I'm playing with Live Bindings and looking at how far I can push it 🙂 ... I wanted to see if I can take a single-item source (edit box) and bind it to a list-type control (list box), somehow splitting the data in the process. This is what I ended at: 1. Bind expression that binds Edit1, Text to ListBox1, Items.DelimitedText. 2. OnAssigningValue event for that bind expression: procedure TForm85.BindExpression1AssigningValue(Sender: TObject; AssignValueRec: TBindingAssignValueRec; var Value: TValue; var Handled: Boolean); begin Value := StringReplace(Value.AsString, ', ', ',', [rfReplaceAll]); end; (This allows me to type in "1, 2, 3" or "1,2,3" and both split into "1", "2", and "3".) 3. Edit1.OnChangeTracking which updates the expression: procedure TForm85.Edit1ChangeTracking(Sender: TObject); begin BindingsList1.Notify(Sender, 'Text'); end; 4. Some initialization for DelimitedText to work correctly: ListBox1.Items.Delimiter := ','; ListBox1.Items.StrictDelimiter := true; Demo project is attached. Does anyone see a better solution for that? (Better = requires writing less code.) (No need to tell me that my problem is stupid and that I should redesign my app so that I don't have to do any splitting of data. I know that. This is an exercise.) liveb.zip
  21. Primož Gabrijelčič

    Generating one-to-many on the fly with Live Bindings

    Nice additions, Andrea 🙂 Whether for the good or bad, who can tell 😉 My initial approach was trying to get too much from LiveBindings anyway (intentionally). Your changes are definitely in direction of making my messy code more maintainable so they make it better. They, however, introduce more code, so from my original viewpoint they make it worse. 😉
  22. No, and don't expect one soon. There are more urgent issues to be fixed before.
  23. Primož Gabrijelčič

    Freeing tasks memory

    Great that you got it working! Re 4000 tasks I was thinking more about - what if you set up a Parallel.BackgroundWorker with multiple workers and then schedule work units to it? You would have one always running execution engine (i.e. background worker) and instead of 4000 tasks you would have 4000 work units.
  24. Primož Gabrijelčič

    Object destroyed too soon?

    Dear all, I'm being stupid and I can't figure why in the following code the `TTestObj` is destroyed before its owner. Can you please help? Full code is available here and is also posted at the end of this post. Basically, I have an interface `IWorkItem` which owns record `TOmniValue` which owns interface `IAutoDestroyObject`. The code creates an instance of `IWorkItem` and then calls: procedure Test(const workItem: IWorkItem); begin workItem.Result.AsOwnedObject := TTestObj.Create; end; This sets the `IAutoDestroyObject` field of the nested `TOmniValue` record. When the `Test` function exits (during the procedure finalization code) this `TTestObj` gets destroyed, and I can't figure out why. program ObjectDestroyedTooSoon; {$APPTYPE CONSOLE} {$R *.res} uses Winapi.Windows, System.SysUtils; type TTestObj = class public destructor Destroy; override; end; IAutoDestroyObject = interface ['{30CF4CCF-9383-41C0-BBA3-E24F7C4EFF71}'] function GetObj: TObject; property Obj: TObject read GetObj; end; TAutoDestroyObject = class(TInterfacedObject, IAutoDestroyObject) strict private FObject: TObject; protected function GetObj: TObject; public constructor Create(obj: TObject); destructor Destroy; override; property Obj: TObject read GetObj; end; TOmniValue = record private FOwnedObject: IAutoDestroyObject; function GetAsOwnedObject: TObject; procedure SetAsOwnedObject(const Value: TObject); public property AsOwnedObject: TObject read GetAsOwnedObject write SetAsOwnedObject; end; IWorkItem = interface ['{7C583FC8-90DD-46A5-81B9-81B911AA1CBE}'] function GetResult: TOmniValue; procedure SetResult(const Value: TOmniValue); property Result: TOmniValue read GetResult write SetResult; end; TWorkItem = class(TInterfacedObject, IWorkItem) strict private FResult: TOmniValue; strict protected function GetResult: TOmniValue; procedure SetResult(const Value: TOmniValue); public destructor Destroy; override; property Result: TOmniValue read GetResult write SetResult; end; { TTestObj } destructor TTestObj.Destroy; begin Writeln('TTestObj destroyed'); inherited; end; { TWorkItem } destructor TWorkItem.Destroy; begin Writeln('TWorkItem destroyed'); inherited; end; function TWorkItem.GetResult: TOmniValue; begin Result := FResult; end; procedure TWorkItem.SetResult(const Value: TOmniValue); begin FResult := Value; end; { TOmniValue } function TOmniValue.GetAsOwnedObject: TObject; begin Result := FOwnedObject.Obj; end; procedure TOmniValue.SetAsOwnedObject(const Value: TObject); begin FOwnedObject := TAutoDestroyObject.Create(Value); end; { TAutoDestroyObject } constructor TAutoDestroyObject.Create(obj: TObject); begin inherited Create; FObject := obj; end; destructor TAutoDestroyObject.Destroy; begin FreeAndNil(FObject); inherited; end; function TAutoDestroyObject.GetObj: TObject; begin Result := FObject; end; { main } procedure Test(const workItem: IWorkItem); begin workItem.Result.AsOwnedObject := TTestObj.Create; end; var workItem: IWorkItem; begin try workItem := TWorkItem.Create; Test(workItem); Writeln('After Test'); workItem := nil; Writeln('workItem destroyed'); Readln; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.
  25. Primož Gabrijelčič

    Object destroyed too soon?

    Got it. type POmniValue = ^TOmniValue; TOmniValue = record private FOwnedObject: IAutoDestroyObject; function GetAsOwnedObject: TObject; procedure SetAsOwnedObject(const Value: TObject); public class operator Implicit(var ov: TOmniValue): POmniValue; static; property AsOwnedObject: TObject read GetAsOwnedObject write SetAsOwnedObject; end; IWorkItem = interface ['{7C583FC8-90DD-46A5-81B9-81B911AA1CBE}'] function GetResult: POmniValue; procedure SetResult(const Value: POmniValue); property Result: POmniValue read GetResult write SetResult; end; TWorkItem = class(TInterfacedObject, IWorkItem) strict private FResult: TOmniValue; strict protected function GetResult: POmniValue; procedure SetResult(const Value: POmniValue); public destructor Destroy; override; property Result: POmniValue read GetResult write SetResult; end; class operator TOmniValue.Implicit(var ov: TOmniValue): POmniValue; begin Result := @ov; end; function TWorkItem.GetResult: POmniValue; begin Result := @FResult; end; procedure TWorkItem.SetResult(const Value: POmniValue); begin FResult := Value^; end; It's quite possible that this breaks quite easily, though, as this solution looks quite fragile to me. It is simple and efficient, though. full code here
×