Sonntag, 2. Dezember 2018

{$IFDEF Version Problem

Inspired by Thomas Mueller (dummzeuch) here is a repost of my ifdef version maintained since 2015.

Since Day one of my FDK I had the same problem : How to maintain the distribution with different delphi versions.

The reason for writing my FDK is/was a better and faster development of iOS and Android apps, but after a short while it became more and more like a Swiss-knife for my daily work.

Since XE 2 all my new Programms are written with FMX and since 2015 I've used it every day for develop my Windows Programms, too.

So here is my version of the {$I Versions.inc}

unit Delphi.VersionConsts;

// =====>     FFFFFF  DDDDD   K    K    FireMonkey Development Kit
// ====>     F       D    D  K  KK        (c) F. Lauter   aka Mavarik
// ===>     FFFF    D    D  KKK               O. Münzberg aka Sir Rufo
// ==>     F       D    D  K  KK
// =>     F       DDDDD   K    K        Version 2015, 2016, 2017, 2018

{******************************************************************************
 * Changes:
 * 01.10.15 Everybody should use this Unit - I hate the $I Version.inc files
 *          I hope that someday EMBT will always provide this Unit with every
 *          new Version.
 ******************************************************************************}

interface

{$IF Declared(CompilerVersion)}
{$ELSE}
const
  CompilerVersion = 0.0;
{$ENDIF}

const
  CompilerVersion_RIO     = 33.0;
  CompilerVersion_TOKYO   = 32.0;
  CompilerVersion_BERLIN  = 31.0;
  CompilerVersion_SEATTLE = 30.0;
  CompilerVersion_XE8     = 29.0;
  CompilerVersion_XE7     = 28.0;
  CompilerVersion_XE6     = 27.0;
  CompilerVersion_XE5     = 26.0;
  CompilerVersion_XE4     = 25.0;
  CompilerVersion_XE3     = 24.0;
  CompilerVersion_XE2     = 23.0;
  CompilerVersion_XE      = 22.0;
  CompilerVersion_2010    = 21.0;
  CompilerVersion_2009    = 20.0;
  CompilerVersion_2007    = 18.5;
  CompilerVersion_2006    = 18.0;
  CompilerVersion_2005    = 17.0;
  CompilerVersion_8_NET   = 16.0;
  CompilerVersion_7       = 15.0;
  CompilerVersion_6_2     = 14.2;
  CompilerVersion_6_1     = 14.1;
  CompilerVersion_6       = 14.0;

const
  Compiler_RIO     = CompilerVersion = CompilerVersion_RIO;   // Carnival
  Compiler_TOKYO   = CompilerVersion = CompilerVersion_TOKYO; // Godzilla
  Compiler_BERLIN  = CompilerVersion = CompilerVersion_BERLIN;
  Compiler_SEATTLE = CompilerVersion = CompilerVersion_SEATTLE;
  Compiler_XE8     = CompilerVersion = CompilerVersion_XE8;
  Compiler_XE7     = CompilerVersion = CompilerVersion_XE7;
  Compiler_XE6     = CompilerVersion = CompilerVersion_XE6;
  Compiler_XE5     = CompilerVersion = CompilerVersion_XE5;
  Compiler_XE4     = CompilerVersion = CompilerVersion_XE4;
  Compiler_XE3     = CompilerVersion = CompilerVersion_XE3;
  Compiler_XE2     = CompilerVersion = CompilerVersion_XE2;
  Compiler_XE      = CompilerVersion = CompilerVersion_XE;
  Compiler_2010    = CompilerVersion = CompilerVersion_2010;
  Compiler_2009    = CompilerVersion = CompilerVersion_2009;
  Compiler_2007    = CompilerVersion = CompilerVersion_2007;
  Compiler_2006    = CompilerVersion = CompilerVersion_2006;
  Compiler_2005    = CompilerVersion = CompilerVersion_2005;
  Compiler_8_NET   = CompilerVersion = CompilerVersion_8_NET;
  Compiler_7       = CompilerVersion = CompilerVersion_7;
  Compiler_6_2     = CompilerVersion = CompilerVersion_6_2;
  Compiler_6_1     = CompilerVersion = CompilerVersion_6_1;
  Compiler_6       = CompilerVersion = CompilerVersion_6;

const
  {$IF Compiler_RIO}    PackageVersion = '260';StudioVersion = '20.0'; {$ENDIF} 
  {$IF Compiler_TOKYO}  PackageVersion = '250';StudioVersion = '19.0'; {$ENDIF} 
  {$IF Compiler_BERLIN} PackageVersion = '240';StudioVersion = '18.0'; {$ENDIF}
  {$IF Compiler_SEATTLE}PackageVersion = '230';StudioVersion = '17.0'; {$ENDIF}
  {$IF Compiler_XE8}    PackageVersion = '220';StudioVersion = '16.0'; {$ENDIF}
  {$IF Compiler_XE7}    PackageVersion = '210';StudioVersion = '15.0'; {$ENDIF}
  {$IF Compiler_XE6}    PackageVersion = '200';StudioVersion = '14.0'; {$ENDIF}
  {$IF Compiler_XE5}    PackageVersion = '190';StudioVersion = '13.0'; {$ENDIF}
  {$IF Compiler_XE4}    PackageVersion = '180';StudioVersion = '12.0'; {$ENDIF}
  {$IF Compiler_XE3}    PackageVersion = '170';StudioVersion = '11.0'; {$ENDIF}
  {$IF Compiler_XE2}    PackageVersion = '160';StudioVersion = '10.0'; {$ENDIF}
  {$IF Compiler_XE}     PackageVersion = '150';StudioVersion = '9.0'; {$ENDIF}
  {$IF Compiler_2010}   PackageVersion = '140';StudioVersion = '8.0'; {$ENDIF}
  {$IF Compiler_2009}   PackageVersion = '120';StudioVersion = '7.0'; {$ENDIF}
  {$IF Compiler_2007}   PackageVersion = '110';StudioVersion = '6.0'; {$ENDIF}
  {$IF Compiler_2006}   PackageVersion = '100';StudioVersion = '5.0'; {$ENDIF}
  {$IF Compiler_2005}   PackageVersion =  '90';StudioVersion = '4.0'; {$ENDIF}
  {$IF Compiler_8_NET}  PackageVersion =  '80';StudioVersion = '3.0'; {$ENDIF}
  {$IF Compiler_7}      PackageVersion =  '70';StudioVersion = '2.0'; {$ENDIF}
  {$IF Compiler_6_2}    PackageVersion =  '60';StudioVersion = '1.2'; {$ENDIF}
  {$IF Compiler_6_1}    PackageVersion =  '60';StudioVersion = '1.1'; {$ENDIF}
  {$IF Compiler_6}      PackageVersion =  '60';StudioVersion = '1.0'; {$ENDIF}

type
  TDelphiVersion = Record
                     Name          : String;
                     StudioVersion : String;
                   end;

const
  TDelphiVersions : array[0..6] of TDelphiVersion  = ((Name:'Delphi XE6';StudioVersion:'14.0'),
                                                      (Name:'Delphi XE7';StudioVersion:'15.0'),
                                                      (Name:'Delphi XE8';StudioVersion:'16.0'),
                                                      (Name:'Delphi 10 Seattle';StudioVersion:'17.0'),
                                                      (Name:'Delphi 10.1 Berlin';StudioVersion:'18.0'),
                                                      (Name:'Delphi 10.2 Tokyo';StudioVersion:'19.0'),
                                                      (Name:'Delphi 10.3 Rio';StudioVersion:'20.0'));

const
  Compiler_RIO_UP     = CompilerVersion >= CompilerVersion_RIO;
  Compiler_TOKYO_UP   = CompilerVersion >= CompilerVersion_TOKYO;
  Compiler_BERLIN_UP  = CompilerVersion >= CompilerVersion_BERLIN;
  Compiler_SEATTLE_UP = CompilerVersion >= CompilerVersion_SEATTLE;
  Compiler_XE8_UP     = CompilerVersion >= CompilerVersion_XE8;
  Compiler_XE7_UP     = CompilerVersion >= CompilerVersion_XE7;
  Compiler_XE6_UP     = CompilerVersion >= CompilerVersion_XE6;
  Compiler_XE5_UP     = CompilerVersion >= CompilerVersion_XE5;
  Compiler_XE4_UP     = CompilerVersion >= CompilerVersion_XE4;
  Compiler_XE3_UP     = CompilerVersion >= CompilerVersion_XE3;
  Compiler_XE2_UP     = CompilerVersion >= CompilerVersion_XE2;
  Compiler_XE_UP      = CompilerVersion >= CompilerVersion_XE;
  Compiler_2010_UP    = CompilerVersion >= CompilerVersion_2010;
  Compiler_2009_UP    = CompilerVersion >= CompilerVersion_2009;
  Compiler_2007_UP    = CompilerVersion >= CompilerVersion_2007;
  Compiler_2006_UP    = CompilerVersion >= CompilerVersion_2006;
  Compiler_2005_UP    = CompilerVersion >= CompilerVersion_2005;
  Compiler_8_NET_UP   = CompilerVersion >= CompilerVersion_8_NET;
  Compiler_7_UP       = CompilerVersion >= CompilerVersion_7;
  Compiler_6_2_UP     = CompilerVersion >= CompilerVersion_6_2;
  Compiler_6_1_UP     = CompilerVersion >= CompilerVersion_6_1;
  Compiler_6_UP       = CompilerVersion >= CompilerVersion_6;

  Compiler_RIO_DOWN     = CompilerVersion <= CompilerVersion_RIO;
  Compiler_TOKYO_DOWN   = CompilerVersion <= CompilerVersion_TOKYO;
  Compiler_BERLIN_DOWN  = CompilerVersion <= CompilerVersion_BERLIN;
  Compiler_SEATTLE_DOWN = CompilerVersion <= CompilerVersion_SEATTLE;
  Compiler_XE8_DOWN     = CompilerVersion <= CompilerVersion_XE8;
  Compiler_XE7_DOWN     = CompilerVersion <= CompilerVersion_XE7;
  Compiler_XE6_DOWN     = CompilerVersion <= CompilerVersion_XE6;
  Compiler_XE5_DOWN     = CompilerVersion <= CompilerVersion_XE5;
  Compiler_XE4_DOWN     = CompilerVersion <= CompilerVersion_XE4;
  Compiler_XE3_DOWN     = CompilerVersion <= CompilerVersion_XE3;
  Compiler_XE2_DOWN     = CompilerVersion <= CompilerVersion_XE2;
  Compiler_XE_DOWN      = CompilerVersion <= CompilerVersion_XE;
  Compiler_2010_DOWN    = CompilerVersion <= CompilerVersion_2010;
  Compiler_2009_DOWN    = CompilerVersion <= CompilerVersion_2009;
  Compiler_2007_DOWN    = CompilerVersion <= CompilerVersion_2007;
  Compiler_2006_DOWN    = CompilerVersion <= CompilerVersion_2006;
  Compiler_2005_DOWN    = CompilerVersion <= CompilerVersion_2005;
  Compiler_8_NET_DOWN   = CompilerVersion <= CompilerVersion_8_NET;
  Compiler_7_DOWN       = CompilerVersion <= CompilerVersion_7;
  Compiler_6_2_DOWN     = CompilerVersion <= CompilerVersion_6_2;
  Compiler_6_1_DOWN     = CompilerVersion <= CompilerVersion_6_1;
  Compiler_6_DOWN       = CompilerVersion <= CompilerVersion_6;

{$IF DEFINED( IOS ) or DEFINED(ANDROID)}

type
   Compiler = class sealed
     public
       Const
         HasArc      = true;
   end;

const
  CompilerIsMobil     = true;

  PlattFormOSX        = false;
  SupportsAnsiStrings = false; //Compiler_BERLIN_UP;
{$DEFINE CompilerIsMobil}

{$ELSE}
type
   Compiler = class sealed
     public
       Const
         HasArc      = false;
   end;

const
{$IFDEF MACOS}
  PlattFormOSX        = true;
{$ENDIF}

{$REGION 'Documentation'}
  /// <summary>
  ///   true if target ist iOS or Android
  /// </summary>
{$ENDREGION}
  CompilerIsMobil     = false;
  SupportsAnsiStrings = true;
{$ENDIF}

implementation

end.




Dienstag, 16. Oktober 2018

This was the beginning...Buy Books!

Looking back on my developments, I was always proud of my software. 

Starting with a programmable calculator and Eumel on a ELAN System in 1981, I got my first computer a Sharp MZ-700 in 1982.



First of all - With learning Basic from the MZ-700 manual, I was able to sell my first software - a graphic editor - to a local dealer to get my first floppy drive.

My first book: Rodnay Zaks programming the Z80. 

My next Computer was the MZ-800! With the MZ-800 graphics, 80 Chars per line was possible and with this, CP/M could be used for development. The Editor - of course - Wordstar, assembler M80, linker L80. At the same time: Learning UCSD-Pascal on Apple II in school and with Turbo-Pascal for CP/M at home.

I decided to write my second program in pure assembler:

The FLDOS  (Frank Lauter's Disk Operation System) 


Thanks to a very kind employee of  Motorola - I got a original manual of the floppy-controller-chip. But this journey is an other story. 

This "operating system" only take 1000 bytes of memory and was able to handle two parts. One for file-operation and the other was a Disk-Hex-Editor. Both parts could be reloaded from floppy without violating the 1000 bytes limit.

The Z80-Book "payed" the start of my development equipment. A dual floppy drive! One with 40 Tracks, one with 80 Tracks. ~2400 DM at this time.

After that I never tried to develop a whole program in pure assembler, because everything else was written in Turbo-Pascal. (Until today - I ignore my tries in C)

I have bought many books during these days, but nearly all books did not satisfy my needs. 700 or more Pages but only 60 pages of useful information - or less... So I had to buy 10 Books to get all the necessary information. (Perhaps that's why I stopped buying books)

And this leads to my next blog post ;-) or back to my Bookwriters post.





Mittwoch, 3. Oktober 2018

Pattern, naming and MVVM from a Delphi point of view.

Pattern are good tools and if you name it correct, other developer should know what you are talking about.

Name it singleton and every developer knows how to deal with it... (I hope). If you are talking about dependency injection, perhaps you are not using it, but at least you can google it. So giving things the right name is always a good thing.

So let's take a look at the development of a new FMX application. Of course, you want modern design, maintainable code and testable business-logic. I think you are looking for the MVVM pattern. Model, View, ViewModel, but after File, New, Multi-device Application you have a project and a Mainform and with a closer look into your tool palette, you are unable to find a View, a Model or even a ViewModel. Nothing to put on your form.

BTW.: Clicking non-visual controls on your form is the worst thing you can do with the goal of MVVM.

And now? What to do? Google for a library?. Perhaps you find something and using a library is normally better than coding all by your self. Do you remember the famous words?

Using is faster than coding!

There are many useful libraries out there! Some are free, some are expensive and some are necessary. I'll never start a new project without my FDK. ;-) Especially when dealing with FMX. So every new project! My last VCL project/tool was with XE. 

Perhaps you are already using my FDK, but unfortunately my MVVM-Plugin is still not available today. Why? Because I'm still dealing with different MVVM-pattern-designs.
After "MVVM was the start" in 2015, "MVVM or what I think it is" in 2016, trying attributes in 2017, "MVVM 2.0 I did it my way" in April 2018 and some kind of different Bindings (no blog post - let's call it MVVM 2.5) -  I'm still working on the "best" way to do it right. I thought I was "short on final" with my View to CRUD-DB Binding pattern, until last month "Sir Rufo" came up with a new binding approach. This with my last attempt will be the final one - I think - today... ;-)

At the moment I have 10 different Apps with my MVVM-Stuff from 2015,2016,2017 and 2018 and of course no MVVM-library form each year is compatible with the others. 4 different Libraries that are used in - at least - 2 Apps... Very bad to maintain. But back to Naming...

We are delphi developers and I never had a View. I have forms, frames - perhaps I use panels or layouts to do visual tricks. Or on iOS & Android I use TabControls to display different - ok call it views. But why name it View? It's a Form... Because a Form is not a View and a Form could display many View's, but this View's are Layouts or Frames? Confusing... Starting in 2015 I had no clue why a Form is not a View - now I understand, but perhaps I will change the names... ;-)

Let's do some examples.

You have:

- TPerson with Name, Day of birth and more
- TAddress with Street, Zip, Town...
- TCommunication with Fon, Mail and more
- TBank with Bankname and Account
- TInvoice with Invoice Fields
- TArticle with Title, Price, weight

In 99,9% all Visual-Controls on my Forms representing a Field in the Database. The Database has - of course - more fields like: Timestamp, CreationDate, LockOwner and more.

Perhaps my DB_Table (Person) is a TPerson,TAddress,TCommunication,TBank in one table. 80%

Perhaps I have TPerson, TAddress1, TAddress2, TFon1, TFon2, TBank1, TBank2, TBank3 100% until 2010 in one record written as binary to file with index. *lol*

Today I would have one DB-Table for TPerson, one for TAddress, one for TCommuniction  and one for TBank.

For this example I like to display this kind of data direct in Form-Fields (TEdit,TCombobox, TCheckBox etc.) and not in a Grid. Perhaps at the bottom a grid with the TInvoice and on dblclick display the Invoice on a second form with the containing TArticle(s).

So what do we have?

A FORM with Field separated in 5 subFRAMES, each subframe depends on a Databasetable. To Display more than one TAddress I had to create the controls at runtime on a scollbox. 
F for Form or Frame and D for Database.

Rule of thumb: Never use visual controls to keep/store your Data. 

That's why we need some class/Memory for our Fields. (That's why I would never use a Live-DB-Connection, where Edit's could only display the data if you have a open connection.) ARC on iOS & Android and no ARC on Windows - so wie need an InterfacedObject as Memory - representation of our Form / Table. One last thing is something to Control the Dataflow from From (TEdit) to Memory -> to Database. Do we have a new Name?

FCMD - Form,Controller,Memory,Database.

FCMD - Sound like my new MVVM... ;-) or is it MVC?

Btw: What was the reason to do MVVM? Ahh yes... to have a good layer separation and testable units with - at the best - no dependencies.

Because we all doing unit-test... ;-)

What is your unit test coverage? 3%, 30%, 70% more?

OK - assume 100% coverage of you business units. Do you have unit tests for your ViewModel?
Maybe some time we got an IDE that could display the unit test coverage.

Back to the Form - we have a grid with a list of invoices - the grid has it's own model inside. Each row could be a Viewmodel - InterfacedObject as Memory-Representation of the Database. So we need a Grid-Controller for displaying the TArray<IInvoice> perhaps with paging and background thread reloading new rows... Again a Controller to move n rows from Memory to the Grid-(View)Model.
And an Model "InterfacedObject Multiloader" to load all Invoices with
"Select * from InvoiceDB where Person = Person.GUID". 

On a DBLClick we could create a new Form and inject the IInvoice from GridHelper(ARow).GetRow<IInvoice> into the Controller of the new Form. Bingo... Working Demo.

So why FCMD and not MVVM or is FCMD another name for MVC?

To answer the question we have to look at the bindings.

In MVC the user uses the Controller to manipulate the Model that updates the View.
In MVVM then View has a bidirectional binding to the ViewModel and the ViewModel has a bidirectional binding to the Model. Is the Model the Database - no. Where is the data stored? In the ViewModel or in the Model? Ask 10 developer and you got 10 different answers. My ViewModels also have the Data and the Model has "all-Data" or the Interface for storing the Data via DB or REST or SOAP or Whatever.
In FCMD you have the Form and the RAD-approach. With the assignment of the ViewModel Memory-Representation to the View Form, you have to create your bindings to the Memory-Representation. This creates the Bind-Controller. If you type something into a TEdit the Bind-Controller transfers the data direct into your Model TInterfacedObject. This Object fires a propertychanged over the viewmodel  Bind-Controller direct to the view - Form. If you reload  or load a different data, your View Form also displays the new content. The Memory Object has all Fields of the TableRow with some Attributes to Create and Handle the Database with CRUD I/O.
Perhaps we change from C for Controller to B for Binding.
So with FBMDR you could write your data to a local DB and in Background over REST to a Server. That's why my CRUD/ORM Memory-Representation could transfer all field to and from JSON.
Btw. Form-Bind-Memory-Database sound like an inmemory database. So change the D to C like in CRUDTable

At this point no unit tests for the View Form-Logic, because there is no ViewModel. Perhaps parts of the ViewModel stuff is in the MemoryObject like "CanSave/CanDelete/Modified" and so on. The other things could be sent via the Bind-Controller or with TMessages into the View Form. Like DisableButton. 

With FBMC(R) 99,9% is testable like in MVVM but 70% less source code to type and 60% less Units.

I would love to see FBMC spread all over the Delphi-Community and beyond,

#FBMC - Form-Bind(ed)-Memory-CRUDTable


or perhaps I do some modifications and call it MVVM 2.5 - again...


The Binding-Controller not only handles the transfer, "he" is also able to do some data conversion, like string to integer or String to TDatetime or A->B, B->A.

So far so good. But with our new FMX-App we need more. Nearly every FMX-Control has some animations build into the style. This animation are handled with timers - of course in the main thread. Like MouseDown, MouseOver, MouseUp and so on. If you are new to FMX you would write:

Procedure TButton1Click(Sender : TObject)
begin
  Button1.Enabled := false;
  DoSomeStuffFor20Sec;
  Button1.Enabled := true;
end;

If you click on Button1 the button is still enabled- you get no MouseDown animation your UI has no response and after 20Sec you can - perhaps - see MouseDown, MouseUp. But things are getting worse.

Procedure TButton1Click(Sender : TObject)
begin
  Button1.Enabled := false;
  NewForm := TNewForm.Create(Application);
end;

If you free the Form that contains the Button1 in the TNewForm Show or Create Event. You'll get an exception, because the MouseUp-Animation will be displayed after the TNewForm.Create is called on a no more assigned Button1.

Nearly everything you want to do like the good old RAD-Days you have to do asynchronous. At best, in the onIdle-Event for UI-Stuff or in Thread with other things.

Procedure TButton1Click(Sender : TObject)
begin
  Button1.Enabled := false;
  Button1.Text    := 'Processing...';

  TAsync.Await(Procedure
    begin // Thread Context
      DoSomeStuffFor20Sec;
    end).
  Execute(Procedure
    begin  
      Button1.Enabled := true;
      Button1.Text    := 'Save'; 
    end);
end;

OR

Procedure TButton1Click(Sender : TObject)
begin
  Button1.Enabled := false;
  Button1.Text    := 'Processing...';

  TIdleWorker.Execute(Procedure
    begin // UI Thread 
      DoSomeStuffWithUI;
      Button1.Enabled := true;
      Button1.Text    := 'Save'; 
    end);
end;

With the Bind-Controller from #FBMC you could bind a command/Method at the Button that can handle this kind of action without the need of writing additional lines of code.

With all these and the additional JSON Import/Export, REST Client/Server Unit you are able to Build your Multi-Device-App with "Cloud-Dataexchange" in minutes. At the moment it works perfect with my Windows-Server. 

Next step is to test the Serverside in a Linux-Docker-Container...

But every Day is too short... So stay tuned...

Freitag, 28. September 2018

Neural Network - Part 2 is coming

First of all : I'm Sorry about by short blogpost from march 2018 about Neural Networks.

It is the second most read post I've ever done and it has nearly no information in it...

It is part of my FDK - and normally I do not share this code - but perhaps I will do it this time.

If I have some samples to show I will come back to the topic.

Is wrong always bad?

In software development there are often (too) many ways you can solve a goal.


But how do I find the right way?

Perhaps you saw this approach in other sourcecode or at a conference. You downloaded the code from Bitbucket or GitHub... Perhaps you found it in a book…

I often heard:

- It's the Microsoft way - they know how to do things - don't question this.
- This is a pattern - you have to do it by the book - more intelligent people have hammered this in stone

or the other side

- don't use this - it's part of the language, but do not use this (With, FreeAndNIL, Goto)

Who is able to decide what is right or wrong and if it is "marked" as wrong is it so bad to use or do it this way?
 
Is it really so, that out there are only a very few who set the rules? Yes I think so. I call them Bookwriters or "Head of the Team". Do not get me wrong - this guys are very talented, and they know how to do things. And for our Delphi?
 
The count for  "Head of the Team" is much smaller than on C, C++ or C#. (btw. from my point of view, all other languages do not count, especially if they do not result in CPU binary code)
 
But what if you do it in an other way, what if you like to do it "called wrong" and what if your way is a better way for you
 
My Answer is : Then do it this way.
 
I your code is still: Readable, maintainable, stable and testable. Compile it, ship it and earn you money. 
 
Why should I do things only by the rule book? - But wait… Perhaps another developer don't understand your way and could not use your source or library. Yes you can write it in your documentation and answer RTFM.
 
For a good library or component there is no need to look into the documentation. Is it so? If yes we are all using stuff the is build decades ago… And yes we do, and we're dealing with this every day.
 
You like a small example? OK, find the bug:
 
var
  i : integer;
  d : TDateTime;
begin
   D := now;
   i :=0;
   while i < 30 do
     begin
       incDay(d);
       inc(i);
     end;
end;
 
From my point of view - it is a bug - but the answer is "We don't want to cause problems to existing code" - bug-report closed. So we still  have to look in the documentation. I would have been so happy if they have used this answer at implementing Unicode as default into the compiler.
 
If you are working with a team or writing code that is used by many other developers you have to do things as your audience would expect it. OK... I agree.
 
But I often like to do things my way because it is - from my point of view - the better way. That's way I have my own MVVP 2.5 Pattern, my FluidCreator or Fluent-Creator, my own Binding and not the visual live binding, do things in code and not in the object inspector, create database with a creation pattern, have a wrapper for FireDac, a wrapper for AppTheathering, a wrapper for Bluetooth, a wrapper for Streams, a wrapper for exception handling, many wrapper to do threading, IDictionary and normally everything thread safe…
 
Perhaps one time in the future - i like do this since 20 years - I will write a book about this and become part of the "Bookwriters"...
 
We will see…
 
But first I had to travel to a planet with a slower rotation speed!

Montag, 10. September 2018

Where to do the Synchronisation?

While designing a new class/function there are some thoughts to do.

  1. Could it be useful to others?
  2. Could I uses it in an other Apps?
  3. Could it be useful to have it as TWhatever<T>?
So if one of 1-3 is true - I have a new class/function in one FDK-Unit or perhaps a new Unit at all.

Next thing is: Where would I like to execute this stuff - Main-thread or Background? If  the answer is Background the next question is how! With an existing threads or a new one? Should the User just place it into a TTask.Run?

Threading is a big part of the FDK and there are 14 Units at the moment dealing with threading. Some of them using a basic thread implementation from an other unit, some implementing own Threads because of special needs.

So for this blog post lets assume background operation, but every background execution has some point of interface into the Main-Thread. And here comes the question:

Where to do the synchronization? 


Let's have a little example:

Procedure Button1Click(Sender : TObject)
var
  LMemoText : String;
begin
  Button1.Enabled := false;

  TAsync
  {} .Await( Procedure
               begin
                 LMemoText := THTTPRead.URL(SomeURL); 
               end )
  {} .Execute( Procedure
                 begin
                   Memo1.Lines.Text := LMemoText;
                   Button1.Enabled := true;
                 end );   
end;

So the Await procedure is running in a thread, that's why I set a string and not directly the memo. The Execute procedure is called in MainThread to handle UI-Stuff. So the synchronization is handled internally. It could be different... But the is the common usage of this TAsync. I Could write it so: 

Procedure Button1Click(Sender : TObject)
begin
  Button1.Enabled := false;

  TAsync
  {} .Await( Procedure
             var 
               LMemoText : String;
             begin
               LMemoText := THTTPRead.URL(SomeURL);

               TThread.Queue(NIL,Procedure 
                 begin
                   Memo1.Lines.Text := LMemoText;
                 end);
              end )
  {} .Execute( Procedure
                 begin
                   Button1.Enabled := true;
                 end );   
end;

But in this example the synchronization is done twice...

Back to the question: Is the internal synchronization of the Execute part a good idea? In the example I think yes. 

Sometimes internal synchronization could be a bad idea especially if TThread.Synchronize is used and not Queue but in the next example even Queue did not help, and here is the reason:

Normally I would do any HTTP related things with a callback, but for this example I like to show a Modal-Call of an Async function, just for Demonstration.

Procedure THTTPRead.URL(Const AURL : String) : String;
var
  LEvent : TEvent;
  LResult : String;
begin
  LEvent := TEvent.Create(NIL,true,false,'');
  
  try 
    THTTPTAsync.URL(AURL, Procedure (Const AResult : String)
      begin
        LResult := AResult; 
        LEvent.SetEvent;  
      end);

    LEvent.Wait;
  finally
    LEvent.Free;
  end; 

  Result := LResult;
end;

Don't do this ;-) but if, then hope that the Result procedure is not called in a TThread.Queue, because this would be a dead-lock. If you have a Threaded or Async Class/Function use it always as it was designed for. 

On the other hand I hate to put in a TThread.Queue in every Call-Back that's why I often design an optional parameter.

THTTPTAsync.URL(AURL, Procedure (Const AResult : String)
  begin
    _Result := AResult; 
    LEvent.SetEvent;  
  end,false); // false = no sync

For better code reading perhaps rename the call like

THTTPTAsync.URL_NoSync(AURL, Procedure (Const AResult : String)
  begin
    // Whatever
  end);

or

THTTPTAsync.URL(AURL, Procedure (Const AResult : String)
  begin
    // Whatever
  end,TSync.No); // TSync = (Yes,No);

Honestly, I mostly just use a boolean, but I'll put it on my List for refactoring.

So with this approach I have the best of both...