Archive

Archive for November 4, 2016

Sneak peek at the new Smart RTL

November 4, 2016 2 comments

There are many new and powerful features in the upcoming release of Smart Mobile Studio. And one of the smallest, yet most powerful and useful features Рis the ability to be notified when a value or property of an object is altered.

So far, TVariant has just been a helper class for dealing with typical tasks. Well, perhaps not just typical because under Smart Pascal, variants can map directly to a javascript object (of any type) and allows you direct access to its prototype.

In the next update TVariant has a few new members, most notably: watch() and unwatch().

As their name imply they allow you to be notified whenever a single property, or indeed – a property representing a child object, is altered. It may sound trivial but it’s actually one of those features that lays the foundation for responsive, data-aware controls. But you can use it for many other things as well, like keeping tab’s on async calls that alters some value. Wouldnt it be cool to be notified when a picture has finished loading? Well, TW3Image has events for that, but being able to achieve the same results with different means is cool. Well, now you can!

Here is how you can use it:

  // create an empty javascript object
  var MyValue: Variant;
  MyValue := TVariant.CreateObject();
  MyValue.NewProperty := 12; // add a property and set a value

  // We want to know when the object changes
  TVariant.Watch(MyValue, "NewProperty", procedure ()
    begin
      showmessage("You changed the value!");
    end);

  // Wait 2 seconds and then change the value
  TW3Dispatch.Execute( procedure ()
    begin
      MyValue.NewProperty := 24;
    end, 2000);

The above code produces, as expected, a message dialog with the text “You changed the value!” 2 seconds after. So the moment “MyValue.NewProperty := 24” is called, the underlying mechanism picks that up and informs you of it through an anonymous call. Removing a watch is equally simple:

  // Remove the watchdog callback from the javascript prototype
  TVariant.UnWatch(MyValue, "Newproperty");

As of writing only Firefox supports this natively: but dont worry! We use the now universal and standard polyfill written by Eli Grey. So this polyfill is compiled into your application regardless. Its essentially now a standard part of the RTL. We have tested it on iOS, Android, Chrome, Internet Explorer, Spartan and even node.js and it performs brilliantly with little or no penalty with regards to speed. The polyfill simply injects itself as a new getter/setter and keeps track of the old, that’s how it can intercept values without causing problems with the intrinsic mechanisms in the JSVM. For those interested, here is the polyfill:

/*
 * object.watch polyfill
 *
 * 2012-04-03
 *
 * By Eli Grey, http://eligrey.com
 * Public Domain.
 * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
 */

// object.watch
if (!Object.prototype.watch) {
	Object.defineProperty(Object.prototype, "watch", {
		  enumerable: false
		, configurable: true
		, writable: false
		, value: function (prop, handler) {
			var
			  oldval = this[prop]
			, newval = oldval
			, getter = function () {
				return newval;
			}
			, setter = function (val) {
				oldval = newval;
				return newval = handler.call(this, prop, oldval, val);
			}
			;
			
			if (delete this[prop]) { // can't watch constants
				Object.defineProperty(this, prop, {
					  get: getter
					, set: setter
					, enumerable: true
					, configurable: true
				});
			}
		}
	});
}

// object.unwatch
if (!Object.prototype.unwatch) {
	Object.defineProperty(Object.prototype, "unwatch", {
		  enumerable: false
		, configurable: true
		, writable: false
		, value: function (prop) {
			var val = this[prop];
			delete this[prop]; // remove accessors
			this[prop] = val;
		}
	});
}

TW3Dispatch

Did you notice that I used TW3Dispatch.Execute() rather than the old w3_callback() function? Indeed. Part of the cleanup we are doing is not just about new and fancy code. Its also about removing duplicate code (which we had a lot of) and to organize as much as possible into classes or namespaces.

TW3Dispatch is a class that deals with delayed execution of code. It is stored in the new System.Time.pas unit and is marked as a partial class. Here is what the basis looks like under System.Time.pas:

  TW3DispatchHandle = THandle;

  (* This class is a thin wrapper around the essential JavaScript callback
     and time-scheduling methods (for delayed or repeated execution of code).
     It is isolated here to protect the user against changes in future revisions
     of javascript, and also different environments that has not yet
     reached the market. This gives us space to adapt to changes without
     affecting your codebase *)
  TW3Dispatch = partial class
    class function Execute(const EntryPoint:TProcedureRef;
          const WaitForInMs: integer): TW3DispatchHandle;overload;

    class procedure CancelExecute(const Handle: TW3DispatchHandle);

    class procedure RepeatExecute(const Entrypoint: TProcedureRef;
            const RepeatCount: integer;
            const IntervalInMs: integer);

    class function SetTimeOut(const Entrypoint: TProcedureRef;
      const WaitForInMs: integer): TW3DispatchHandle;

    class procedure ClearTimeOut(const Handle: TW3DispatchHandle);

    class function SetInterval(const Entrypoint: TProcedureRef;
      const IntervalDelayInMS: integer): TW3DispatchHandle;

    class procedure ClearInterval(const Handle: TW3DispatchHandle);

    class function JsNow: JDate;

    class function Ticks: integer;
    class function TicksOf(const Present: TDateTime): integer;
    class function TicksBetween(const Past, Future: TDateTime): integer;
  end;

The reason the class is marked as partial, is because depending on the framework you are using (SmartNJ for node.js, or SmartCL for visual applications), TW3Dispatch is expanded accordingly. If we move over to the SmartCL RTL folder and look at the file SmartCL.System.pas (yes, the same units exist in the different namespaces), TW3Dispatch gains the following, DOM related functionality:

  (* TW3Dispatch is defined in the System.Time unit.
     We complete the partial class here with DOM spesific functionality *)
  TW3Dispatch = partial class
  public
    class function Ready:Boolean;
    class procedure ExecuteDocumentReady(const OnReady:TProcedureRef);
    class function RequestAnimationFrame(const Entrypoint: TProcedureRef): TW3DispatchHandle;virtual;
    class procedure CancelRequestAnimationFrame(const Reference: TW3DispatchHandle);virtual;
  end;

The Ready() function is a handy one, it tells you if the browser has finished loading associated scripts and resources and that the document object model is, well, ready to be used. But while that is handy (especially in Form.Initialization() and similar spots that can be called while the DOM is not yet finished), ExecuteDocumentReady() makes it almost fun to write controls and get the startup of your application synchronized.

As you probably know JavaScript is ASYNC by nature. This means that any attempt to force it to behave like traditional object pascal is a gamble at best. Earlier you had to make due with a timer callback to give you some delay before performing a graphics, but now you can use ExecuteDocumentReady() to make sure everything is in order before you execute that special piece of code. It takes a single anonymous procedure, like this:

  TW3Dispatch.ExecuteDocumentReady( procedure ()
    begin
      showmessage("Its now safe to modify the DOM!");
    end);

Now the amount of changes and alterations to the RTL is in the hundreds, so explaining them all here is beyond the scope of this post. But here are a few more classes that makes your life easier:

  TW3MouseCursor = static class
  public
    class function  CursorByName(const CursorName: string): TCursor;
    class function  NameByCursor(const Cursor: TCursor): String;
    class function  GetCursorFromElement(const Handle: TControlHandle): TCursor;
    class procedure SetCursorForElement
      (const Handle: TControlHandle; const Cursor: TCursor);
  end;

  TW3DOMDeviceCapabilities = class(TW3CustomDeviceCapabilities)
  protected
    function GetMouseSupport: boolean;override;
    function GetTouchSupport: boolean;override;
    function GetGamePadSupport: boolean;override;
    function GetKeyboardSupported: boolean;override;
    function GetDevicePixelRatio: float;virtual;
    function GetDisplayPixelsPerInch: TPixelsPerInch;virtual;
  public
    property  DevicePixelRatio: float read GetDevicePixelRatio;
    property  DisplayPixelsPerInch: TPixelsPerInch read GetDisplayPixelsPerInch;
  end;

  TW3TweenEngine = class
  private
    FTimer:    TW3Timer;
    FValues:   Array of TW3TweenElement;
    FActive:   boolean;
    FPartial:  boolean;
    FInterval: integer;
    FLookup:   TW3ObjDictionary;
    procedure  SetInterval(const Value: integer);
  protected
    procedure HandleSyncUpdate;virtual;
    procedure HandleUpdateTimer(Sender: TObject);virtual;
    function  Update(const Item: TW3TweenElement):double;virtual;
  protected
    property  Dictionary:TW3ObjDictionary read FLookup;
    procedure TweenStarted(const Item:TW3TweenElement);virtual;
    procedure TweenComplete(const Item:TW3TweenElement);virtual;
    procedure TweenPaused(const Item:TW3TweenElement);virtual;
    procedure TweenResumed(const Item:TW3TweenElement);virtual;
  public
    function  ObjectOf(const Id: string):TW3TweenElement;
    function  IndexOf(Id: string): integer;

    property  Active: boolean read ( FActive );
    property  Item[const index: integer]:TW3TweenElement read (FValues[index]);
    property  Tween[const Id: string]:TW3TweenElement read ObjectOf;default;
    property  Count:Integer read ( FValues.Length );
    property  Interval: integer read FInterval write SetInterval;

    property  SyncRefresh: boolean;
    property  IgnoreOscillate: boolean;

    function  Add(Id: string): TW3TweenElement;overload;
    function  Add(Id: string; const StartValue, Distance,Duration: double;
              const EaseObject: TW3TweenEase;
              const Behavior: TW3TweenBehavior): TW3TweenElement;overload;
    function  Add(const Instance: TW3TweenElement): TW3TweenElement;overload;

    procedure Delete(index: integer);overload;
    procedure Delete(Id: string);overload;
    procedure Delete(const IdList: TStrArray);overload;

    procedure Clear;overload;

    procedure Execute;overload;
    procedure Execute(const Finished: TProcedureRef);overload;
    procedure Execute(const TweenObjects: Array of TW3TweenElement);overload;

    procedure Pause(const Index: integer);overload;
    procedure Pause(const Tween: TW3TweenElement);overload;
    procedure Pause(const Objs: array of TW3TweenElement);overload;
    procedure Pause(const Ids: array of string);overload;
    procedure Pause;overload;

    procedure Resume(const index: integer);overload;
    procedure Resume(const Tween: TW3TweenElement);overload;
    procedure Resume(const Objs: array of TW3TweenElement);overload;
    procedure Resume(const Ids: array of String);overload;
    procedure Resume;overload;

    procedure Cancel;overload;

    class function TimeCode: double;

    constructor Create;virtual;
    destructor Destroy;Override;
  published
    property OnPartial:  TW3TweenPartialEvent;
    property OnUpdated:  TW3TweenUpdatedEvent;
    property OnFinished: TW3TweenFinishedEvent;
    property OnStarted:  TW3TweenStartedEvent;
  end;

  TMutationObserver = Class(TObject)
  public
    FObserving:   Boolean;
    FHandle:      THandle;
    FTarget:      THandle;
  protected
    procedure     CBMutationChange(mutationRecordsList:variant);virtual;
  public
    Property      OnDisconnect:TNotifyEvent;
    Property      OnConnect:TNotifyEvent;
    property      OnChanged:TMutationObserverEvent;
    Property      Handle:THandle read FHandle;
    Property      TargetHandle:THandle read FTarget;
    Property      Options:TMutationObserverOptions;
    Property      Observing:Boolean read FObserving;
    Procedure     Observe(targetHandle:THandle);
    Procedure     Disconnect;
    Constructor   Create;virtual;
    Destructor    Destroy;Override;
  end;

  TW3StyleSheet = Class(TObject)
  private
    FHandle:    THandle;
  protected
    function    GetSheet: THandle;
    function    GetRules: THandle;
    function    GetCount: integer;
    function    GetItem(const Index: integer): string;
  public
    Property    Sheet: THandle read GetSheet;
    Property    Handle: THandle read FHandle;

    function    Add(RuleName: string; const aRules: string): string;overload;
    Procedure   Add(const aRuleText: string);overload;

    Property    Count:Integer read GetCount;
    Property    Items[const Index: integer]: string read GetItem;

    class procedure AddClassToElement(const aElement:THandle;const aName:String);
    class procedure RemoveClassFromElement(const aElement:THandle;const aName:String);
    class function  FindClassInElement(const aElement:THandle;const aName:String):Boolean;

    Constructor Create;virtual;
    Destructor  Destroy;Override;
  End;


  TW3CustomAsset = class(TW3OwnedObject, IW3AssetAccess)
  private
    FState:       TW3AssetState;
    FCanceled:    boolean;
    FIdentifier:  TW3AssetIdentifier;
  protected
    procedure BeforeAssetIO;virtual;
    procedure AfterAssetIO;virtual;
    procedure FailedAssetIO;virtual;
    procedure ResetData;virtual;
    procedure SetState(const NewState:TW3AssetState);
    procedure SetIdentifier(const NewIdentifier:TW3AssetIdentifier);
    procedure SetData(const Value: variant);
  protected

    function  ManagerAccess: IW3AssetManagerAccess;

    procedure PerformIO(const ThisURI: String;
      OnReady: TW3AssetReadyEvent;
      OnFailed: TW3AssetIOFailureEvent);virtual;

  public
    Property  Owner: TW3AssetManager read ( TW3AssetManager(inherited Owner) );
    Property  URI: String;
    Property  Data: TW3AssetData;
    Property  State: TW3AssetState read FState;
    property  Identifier: TW3AssetIdentifier read FIdentifier;

    procedure Execute;virtual;
    procedure Cancel;virtual;

    constructor Create(const AOwner: TW3AssetManager);reintroduce;
    destructor  Destroy;override;

  published
    Property  OnBeforeIO: TW3AssetBeforeIOEvent;
    property  OnExecuting: TW3AssetIOEvent;
    property  OnAssetReady: TW3AssetReadyEvent;
    property  OnAssetFailed: TW3AssetIOFailureEvent;
  end;

  TW3AssetManager = class(TObject, IW3AssetManagerAccess)
  private
    FAssets:  array of TW3CustomAsset;
    FRemaining: integer;
    FState:     TW3AssetManagerState;
  protected
    procedure   AssetReady(const Asset:TW3CustomAsset);virtual;
    procedure   AssetFailed(const Asset:TW3CustomAsset);virtual;
    function    GetAssetById(const AssetId: TW3AssetIdentifier): TW3CustomAsset;
  public
    property    Active: boolean read ( (FRemaining>0) and (FState=msLoading) );
    property    State: TW3AssetManagerState read FState;
    Property    RemainingToLoad: Integer read FRemaining;
    property    ReadyToUse: integer read (FAssets.Count - FRemaining);
    property    Assets[const Id:string]:TW3CustomAsset read GetAssetById;
    property    Items[const AssetIndex: integer]:TW3CustomAsset read (FAssets[AssetIndex]);
    property    Count: integer read (FAssets.Count);

    function    Add(const AssetObject: TW3CustomAsset): TW3CustomAsset;

    procedure   LoadAllAssets;
    procedure   Cancel;
    procedure   Reset;
    procedure   Clear;

    destructor  Destroy;override;
  end;

Then we have codecs for dealing with ciphers and conversion in a unified way, and much, much more. But you probably want to see the new node.js high level classes right? Well here is little taste

  TW3NodeFileSystem = class(TW3CustomFileSystem)
  public
    function Examine(const FullPath: String;var Files: TStrArray):boolean; override;
    function Rename(const OldPath, NewPath: String): boolean;override;
    function FileExists(const Filename: string): boolean;override;
    function DirectoryExists(const FullPath: string): boolean;override;
    function QueryFilePermissions(const FullPath: string;var Permissions:TW3FilePermissionMask): boolean;override;
    function CreateDirectory(const FullPath: string; Permissions: TW3FilePermissionMask): boolean;override;
    function DeleteDirectory(const FullPath: string): boolean;override;

    function QueryFilesize(const Filename: string;var Size:integer):Boolean;override;
    procedure LoadBinaryFile(const Filename: string; var Binary: TBinaryData);override;
    procedure LoadTextFile(const Filename: string; var Text: string);override;
    procedure LoadStreamFile(const Filename: string; var Stream: TStream);override;
    procedure SaveBinaryFile(const Filename: string; const Binary: TBinaryData);override;
    procedure SaveTextFile(const Filename: string; const Text: string);override;
    procedure SaveStreamFile(const Filename: string; const Stream: TStream);override;

    procedure ExamineA(const FullPath: String; const CallBack: TW3FileSystemExamineHandler);
    procedure RenameA(const OldPath, NewPath: String; const CallBack: TW3FileSystemErrorHandler);
    procedure FileExistsA(const Filename: string; const Callback: TW3FileSystemExistsHandler);
    procedure DirectoryExistsA(const FullPath: string; const Callback: TW3FileSystemExistsHandler);
    procedure CreateDirectoryA(const FullPath: string; Permissions: TW3FilePermissionMask; const CallBack: TW3FileSystemErrorHandler);
    procedure DeleteDirectoryA(const FullPath: string; const CallBack: TW3FileSystemErrorHandler);
  end;

  TNJServerAfterStartedEvent = procedure (sender: TObject);
  TNJServerAfterStoppedEvent = procedure (sender: TObject);
  TNJServerBeforeStartEvent = procedure (sender: TObject);
  TNJServerBeforeStopEvent = procedure (sender: TObject);

  TNJHandleBasedClass = class(TObject)
  private
    FHandle:  THandle;
  protected
    procedure SetHandle(const NewHandle: THandle); virtual;
    function  GetHandle: THandle; virtual;
  public
    property Handle: THandle read GetHandle;
  end;

  TNJServerChildClass = class(TNJHandleBasedClass)
  private
    FParent: TNJCustomServer;
  public
    property Server: TNJCustomServer read FParent;
    constructor Create(Server: TNJCustomServer); virtual;
  end;

  TNJCustomServerRequest = class(TNJServerChildClass)
  end;

  TNJCustomServerResponse = class(TNJServerChildClass)
  end;

  TNJCustomServer = class(TObject)
  private
    FHandle:  THandle;
    FActive:  boolean;
    FPort:    integer;
  protected
    procedure BeforeStart;virtual;
    procedure AfterStart;virtual;
    procedure BeforeStop;virtual;
    procedure AfterStop;virtual;
  protected
    procedure StartServer;virtual;
    procedure StopServer;virtual;
    function  GetActive: boolean;virtual;
    procedure SetActive(const Value: boolean);virtual;
    function  GetHandle: THandle; virtual;
    procedure SetHandle(const Value: THandle); virtual;
    function  GetPort: integer;virtual;
    procedure SetPort(const Value: integer);virtual;
  public
    property  OnAfterServerStarted: TNJServerAfterStartedEvent;
    property  OnAfterServerStopped: TNJServerAfterStoppedEvent;
    property  OnBeforeServerStarted: TNJServerBeforeStartEvent;
    property  OnBeforeServerStopped: TNJServerBeforeStopEvent;

    property  Active: boolean read GetActive write SetActive;
    property  Port: integer read GetPort write SetPort;

    procedure Start;virtual;
    procedure Stop;virtual;
  end;

  // Base exception for HTTP server(s)
  ENJHttpServerError = class(ENJServerError);

  // Http request-object
  TNJHttpRequest = class(TNJCustomServerRequest)
  private
    FEncoding:  string;
  protected
    function    GetHeaders: TJsonObject;virtual;
    function    GetTrailers: TJsonObject;virtual;

    procedure   SetEncoding(const Value: string); virtual;
    function    GetEncoding: string; virtual;
  public
    property    Socket: JNodeSocket read ( JServerRequest(Handle).connection );
    property    Encoding: string read GetEncoding write SetEncoding;
    property    &Method: string read ( Handle.&method );
    property    Url: string read ( Handle.url );
    property    Headers: TJsonObject read GetHeaders;
    property    Trailers: TJsonObject read GetTrailers;
    property    HttpVersion: string read ( Handle. httpVersion );

    procedure   Pause; virtual;
    procedure   Resume; virtual;

    constructor Create(const Server: TNJCustomServer;
                const RequestObject: JServerRequest); reintroduce; virtual;
  end;

  // Http response-object
  TNJHttpResponse = class(TNJCustomServerRequest)
  protected
    function  GetSendDate: boolean; virtual;
    procedure SetSendDate(const Value: boolean);virtual;
  public
    property  StatusCode: integer read (Handle.statusCode) write (Handle.statusCode);
    property  SendDate: boolean read GetSendDate write SetSendDate;

    function  GetHeader(const Name: string): string;
    procedure SetHeader(const Name: string; const Value: string);
    procedure RemoveHeader(const Name: string);

    function  Write(const Text: string): boolean;overload;
    function  Write(const Text: string; const Encoding: string): boolean;overload;
    procedure Write(const Stream: TStream); overload;
    procedure Write(const Buffer: TBinaryData); overload;

    procedure &End(const Buffer: TBinaryData); overload;
    procedure &End(const Data: variant); overload;
    procedure &End(const Data: variant; const Encoding: string); overload;
    procedure &End(const Stream: TStream);overload;
    procedure &End(const Stream: TStream; const Encoding: string); overload;

    constructor Create(const Server: TNJCustomServer;
                const ResponseObject: JServerResponse); reintroduce; virtual;
  end;

  TNJHTTPServerRequestEvent = procedure (Sender: TObject;
    const Request: TNJHttpRequest;
    const Response: TNJHttpResponse);

  TNJHTTPServer = class(TNJCustomServer)
  private
    procedure InternalSetActive(const Value: boolean);
  protected
    procedure Dispatch(request: JServerRequest;
              response: JServerResponse); virtual;
  protected
    procedure SetActive(const Value: boolean);override;
    procedure StartServer;override;
    procedure StopServer;override;
  public
    property  Instance: JServer read ( JServer(GetHandle));
  published
    property  OnRequest: TNJHTTPServerRequestEvent;
  end;

  TNJWebSocketSocket = class(TObject)
  private
    FSocket:  JWsSocket;
    FServer:  TNJWebSocketServer;
  protected
    function  GetReadyState: JWsReadyState; virtual;
    function  GetSocketName: string; virtual;
    function  GetSocketRequest: TNJHttpRequest; virtual;
  public
    property  ReadyState: JWsReadyState read GetReadyState;
    property  Name: string read GetSocketName;
    property  Server: TNJWebSocketServer read FServer;
    property  Request: TNJHttpRequest read GetSocketRequest;

    property  OnSocketClose: TNJWebSocketSocketNotifyEvent;
    property  OnSocketOpen: TNJWebSocketSocketNotifyEvent;
    property  OnMessage: TNJWebSocketSocketMessageEvent;

    procedure Emit(EventName: string; Data: variant; Attempts: integer);

    constructor Create(const Server: TNJWebSocketServer;
                const WsSocket: JWsSocket); virtual;
    destructor Destroy;override;
  end;

  TNJWebSocketServerTextMessageEvent = procedure (const Sender: TNJWebSocketServer; const Info: TNJWebsocketMessageInfo);
  TNJWebSocketServerBinaryMessageEvent = procedure (const Sender: TNJWebSocketServer; const Info: TNJWebsocketMessageInfo);
  TNJWebSocketServerClientConnected = procedure (const Sender: TNJWebSocketServer; const Socket: JWsSocket);
  TNJWebSocketServerClientDisconnected = procedure (const Sender: TNJWebSocketServer; const Socket: JWsSocket);

  TNJWebSocketOnHandler = procedure (const Socket: JWsSocket; const Data: variant);

  TNJWebSocketServer = class(TNJCustomServer)
  private
    FOnConnected: TNJWebSocketServerClientConnected;
    FOnDisconnected: TNJWebSocketServerClientDisconnected;
    FOnTextMessage: TNJWebSocketServerTextMessageEvent;
    FOnBinMessage: TNJWebSocketServerBinaryMessageEvent;
    FTrack: boolean;
    FPath: string;
    procedure InternalSetActive(const Value: boolean);
  protected
    procedure SetPath(URLPath: string); virtual;
    procedure SetTracking(Value: boolean); virtual;
  protected
    procedure Dispatch(const Data: TNJWebsocketMessageInfo); virtual;
    procedure SetActive(const Value: boolean);override;
    procedure StartServer;override;
    procedure StopServer;override;
  public
    property  Path: string read FPath write SetPath;
    property  ClientTracking: boolean read FTrack write SetTracking;
    procedure Emit(EventName: string; Data: variant; Attempts: integer);
    procedure On(EventName: string; CallBack: TNJWebSocketOnHandler);
  published
    property OnClientConnected: TNJWebSocketServerClientConnected read FOnConnected write FOnConnected;
    property OnClientDisconnected: TNJWebSocketServerClientDisconnected read FOnDisconnected write FOnDisconnected;
    property OnTextMessage: TNJWebSocketServerTextMessageEvent read FOnTextMessage write FOnTextMessage;
    property OnBinMessage: TNJWebSocketServerBinaryMessageEvent read FOnBinMessage write FOnBinMessage;
  end;

I could do this all day. So yeah, there is going to be a few changes. A lot of changes. And you will be amazed at how much cool stuff you can do! While I love all the cool new stuff thats going into SmartCL (the visual application types) I must admit that working with node.js and IOT is what really gives me a kick these days. When it takes less than 1 minute to write a server that can scale, execute in fork or cluster mode and daisy-chained – all of it while making use of the latest technology and API’s — then you have some firepower to play with.

So stay tuned for more, much more ūüôā

The never ending story, PirateLogic evidence claims

November 4, 2016 3 comments

Seems like this train never really stops. Despite me telling my side of the story, people still have a hard time believing that Pilot Logic have been so blatantly bad when it comes breaking the law. Not just with my libraries but also with code belonging to Embarcadero.

Back up my claims? Fine.

Fine. I have just uploaded two compare files (out of several) which shows a 1:1 compare between Embarcadero owned VGScene and PL_Orca. If you still think my colleagues and myself lack evidence, then you simply need to download Codetyphon and all its available packages (especially those >= 2 years old) and look at it yourself. Hopefully this will be enough to prove my point.

No wonder they got upset when we started to dig into their codebase. This is just one of many where license violations is the new standard.

No wonder they got upset when we started to dig into their codebase. This is just one of many where license violations is the new standard.

You can download two HTML reports from our file-compare analysis here: http://quartexhq.myasustor.com/piratelogic/piratelogic.rar

Orca = VgScene = Firemonkey

Needless to say, several people have taken an interest in this case. I am happy that most of those are positive and understand both why I got upset with PilotLogic and also why I wanted to inform the Freepascal and Lazarus forums about the dodgy practices. PL has close ties to the FPC community after all. Why sit and watch a disease infect a healthy codebase?

orca

Considering Orca is a VGScene renamed and 99.9% of the codebase is pure VGScene without change — Sternas is lying his head off. This is a clean copyright violation of Embarcadero IP.

And no, I did not set out to defend this or that – but when people call me a liar without even considering that I may be telling the truth, then I must be given a chance to defend myself. And my motivation was absolutely not¬†to “eliminate the competition” (like some people amazingly declare). What competition? I use Delphi for native work and Smart Pascal for JavaScript. What the hell would Codetyphon have to offer? And why on earth would I be against it when I advocate both freepascal and Delphi pretty much 24/7. The more object pascal compilers and systems the better!

How would you respond when you submit a complaint about your code being taken, and the moderator laughs at you and start to post the address of where you children live? Is that ok in your book? I would imagine most men would respond rather “instinctively” when faced with such actions.

codetyp01

I am so tempted to post the whole nest of pigs, but I will leave it with this. If you have any more questions, just download and investigate the codebase yourself. They have no doubt covered some of their tracks by now Рbut not all of them.

When the response you get from the lead-coder of Codetyphon when posting valid questions about illegal use of your software, broken licenses and piracy is “close and delete topic” – then I guess that says it all.

codetyp02

You are a bully Sternas. But when you hit me, I will hit back. Bullies don’t seem to like that very much huh?

Piracy kills more than companies, it kills the spirit

With regards to the notion that I somehow sent my complaints to PilotLogic because I was coding a new IDE, I have this to say:

Do you know how long it takes to write a usable IDE? It’s not something you slap together over the weekend. The more complex the behavior you want to deliver, the more work needs to be invested. Lazarus have gone through what? A decade of work before it’s finally reaching a point where it’s polished and stable? Smart Mobile Studio is almost five years old, and the designer still needs plenty of work. The fact that we do live rendering of the UI in separate processes makes it much harder than a vanilla “Delphi” like designer.

It takes time to develop a professional IDE. You know why? Because you have to go through the whole evolution of thought. You end up refactoring as the approach, ideas and concepts mature. The alternative is, like Pilot Logic has done, to take the shortcut: to steal the code rather than go through the evolution themselves. To really work on it and figure it out yourself.

If I removed all moral reservations, ignored and disrespected the months and years invested by others, by friends and companies synonymous with object pascal and the community I am part of and love so much – I could probably make an IDE more or less identical to Delphi in a couple of months. If I could just steal code from TMS, Developer Express, Elevate Software and Lazarus and completely ignore the energy and time their products represent – then life would be much easier. Or would it?

But I can’t do that. I’m not wired like that. Sure I have used a pirated copy of something in my life. Im not even going to deny that. But I have always saved up, months if I have to, and bought every single one.

How could I look these people in the eye at some Delphi meetup or congregation knowing that I have robbed them of weeks, months and years? I would not be able to hold my head up straight in shame. Because when you buy a source-code license, it’s not just a “use and throw away” thing. It is a contract of trust, and a legal and moral obligation to use the technology they have offered you within the bounds of their wishes.

I still remember how tired and utterly exhausted I was after releasing Smart Mobile Studio 1.0. I had worked night and day for over a year. Weekends, nights; I’m pretty sure that product broke my marriage to be honest. I even took out a second mortgage on our house. And I was so proud that despite all the challenges involved, despite all the negative people who said it was impossible we found a way and made it happen!

And I was heartbroken when only weeks after the release I found a cracked version of Smart on DelphiFan.com. They must have taken them a couple of weeks at least to crack it, because they had given up on writing a keygen. HexLicense uses non-linear numbers so ordinary brute-force attacks are rarely successful. On closer inspection I noticed that they had disassembled the whole executable and painstakingly isolated enough code, even padded the executable to make the checksum match. So nothing is un-crackable.The only thing you can do is to buy a window of time before it happens. That window is what makes or breaks your income.

What really gutted me was discovering that people I talked with quite often was responsible for this (one of the hackers was a member of Delphi Developer). Over a year of testing, prototyping, co-operation between Eric Grange and myself – not to mention all the work J√łrn Angeltveit from Optimale Systemer had done. He also helped finance the project for 12 long months. We even set the price as low as we possibly could just to make sure piracy would not become an issue. Then a prodigy coder from asia, one that talked friendly with me on a weekly basis, a person I had helped with his Delphi code – decided to allocate time just to destroy our solo release. For what? Five minutes of fame? I would have given him a free copy if he asked me. In fact, I have given away almost as many copies as we have sold. Primarily to kids and people who are in a bad financial spot but who wants to learn pascal.

I can understand that people use pirated software, no problem. But when you see up front the amount of damage it does, especially for a small company – you have to be heartless to continue.

Two weeks is what we got. I lost my house, everything. I had to start from scratch. But it was worth it, every bit of it. Because they said it could not be done.

And that my IDE work, both public and in the labs should in any way pose a threat to Lazarus or Codetyphon, is ridicules. I have a full-time job, two kids and my girlfriend, Smart Mobile Studio on the side – and I have recently started my own company. Not much time left for plotting evil schemes. Darth Vader very thin on the ground. But just why would I want to? What kind of place do you come from where people ruin other people’s work like that? It’s not how I was raised that’s for sure. I just wanted the copyright header in my own code to remain. And it would be nice if you asked rather than just misrepresent my work it as your own. I can’t even believe this is a debate.

Add to that the injury i got 3 years ago when i broke my spine in two places. This has impacted my ability to commute. I have had to say no to several high profile jobs because I would not be able to give 100%. So I declined rather than being a burden. So what kind of person would I be if I sat here plotting to destroy something for others, just to win myself. That is against everything I believe in. And the amount of code, papers and help I have issued over the years should at least give me the benefit of the doubt. Because I havent charged a dime for any of it.

Nor do I sit here and pity myself like a child. If you want it, you will find a way! I was not supposed to walk either – but I’m walking. Funny how the impossible seem to happen around people who refuse to give up. Yet curses rip to pieces the man that fears the dark. Shoot me, stab me, but I will still come after you. No force in nature is stronger than the will of a person who believe in himself. Except the same force in reverse for those that lack vision. All the power of the world is in your own mind. And I will keep doing the impossible until the day that I die – because I can.

End of case

I did not want Pilot Logic’s tendencies to infect Lazarus or freepascal, which it would almost certainly have done had we not blown the whistle on this. So in a way we did succeed. But at a personal cost we should not have had to pay.

Hopefully this time it will stick and the drama factor mellows out.