Archive

Archive for the ‘Tips’ Category

Good Practices for JavaScript “asm” sections in DWS/OP4JS

January 16th, 2012

The compiler supports writing “asm” aka JavaScript section in the middle of Object Pascal, there are a few good practices as well as tips to keep in mind, let’s review the menu:

  1. Name conflicts and obfuscation support
  2. Do you really need an “asm” section?
  3. Don’t rely on implicit parameters structure
  4. Handling callbacks with “Variant” methods
  5. Handling callbacks in an “asm” section
  6. Current limitations

1. Name conflicts and obfuscation support

This should be a point zero actually, but the first thing to have in mind is that you are allowed in Pascal to use as names identifiers that are reserved in JavaScript. Those can be language keywords (“this”, “delete”, etc.) or common DOM objects and properties (“document”, “window”).

The compiler automatically protects you from such conflicts by transparently renaming your identifiers (currently by adding a “$”+number at the end).

Then there is the obfuscator, which will basically rename everything to short, meaningless names. That’s good for more than obfuscation: it reduces the size of the JavaScript, improves the parsing and lookup-based performance in the browser.

The consequence is that in an “asm” section, you should prefix all Pascal identifiers with an ‘@’, so the compiler can correctly compile your asm. For instance in:

var window : String;
...
asm
   @window = window.name
end;

The ‘@window’ refers to the ‘window’ string variable (which the compiler will rename), while ‘window.name’ will be compiled “as is”, as it reads the ‘name’ property of the global ‘window’ JavaScript object.

2. Do you really need an “asm”‘ section?

Though for some weird cases you might (like this gem), there are many cases in which you don’t need “asm”, as the language supports a “Variant” type which is a raw JavaScript object, and upon which you can call methods, read properties directly or via indexes.

For instance, with v a Variant, the following code:

v := v.getNext();
v['hello'] := v.space + 'world';

will get compiled (almost) straight into

v = v.getNext();
v['hello'] = v.space + 'world';

When using Variant, you don’t have strong compile-time checks (it’s just you vs JavaScript), property and function names are case-sensitive, so use them with care. This is similar in syntax and essence to using OLE Variants and Delphi.

On the other hand, you have compiler support, and you get automatic casts when assigning a variant to a strong type (Integer, String, etc.), and you also get name conflict protection & obfuscation support without having to ‘@’ your Pascal references.

3. Don’t rely on implicit parameters structure

Because they may change in future compiler revisions!

For instance, methods are currently invoked with an implicit “Self” parameters, and the others behind, so currently “arguments[0]” is Self, and everything else if after that. But don’t rely on it.

Future compiler revisions may change that parameter’s name, may obfuscate it, may remove it entirely in favor of an implicit “this”, may inline your function, etc.

So if you need explicit parameters, declare them, if you’re in a method and need to access the object (Self), use “@Self”, if you need to access a field of the current object use “@Self.FieldName”, etc. That will keep working.

4. Avoid declaring variables in “asm” sections

Declare them in the parent function/method instead, and reference them with the ‘@’ prefix.

There are three main reasons for that, the first is that doing so means they’ll be case-insensitive, the second is that it will allow the obfuscator to obfuscate them reason for that, and the third is that you’ll get compiler warnings if you declare a variable but do not use it (or if you forgot to @-prefix it).

So don’t write that:

asm
   var myTemp;
   myTemp = ...whatever...;
   ...
end;

But write this instead:

var myTemp : Variant;
...
asm
   @myTemp = ...whatever...;
   ...
end;

5. Handling callbacks with “Variant” methods

A common occurrence is to register a callback to a JavaScript object, when that object is hosted in a Variant, that’s fairly simple to achieve:

procedure DoImageLoaded;
begin
   ...
end;
...
var myImage : Variant; // will refer to an image object
...
myImage.onload(@DoImageLoaded);

There we use the ‘@’ operator Pascal-side, to make it explicit that we want a function pointer, and not call the function. The ‘@’ isn’t necessary when the function is declared Pascal-side, as the compiler can figure it out, but when invoking a Variant method, it doesn’t know the parameters type.

Note that since function pointers are unified, you can get a function pointer from an object method or an interface method in the same fashion:

myImage.onload(@myObject.DoImageLoaded);
myImage.onload(@myInterface.DoImageLoaded);

6. Handling callbacks in an “asm” section

If you want to register the callback in an “asm” section, the situation is a little more complex, as “@myObject.myMethod” will refer to the function prototype, outside of its context. It means it’s okay for standalone functions or procedures, but may not do what you’re expecting for object or interface methods.

The solution is to acquire the function pointer outside of the “asm” section:

var myCallback : Variant;
...
myCallback := @myObject.DoImageLoaded;
asm
   @myImage.onload(@myCallback);
end;

7. Current limitations

Currently the parser for “asm” sections doesn’t really understand JavaScript:

  • it’s still treating JS as a weird invalid form of Pascal, and notably {} define comments for it, so it will pass whatever is inside curlies “as is”, and will annoyingly ignore @ signs inside curlies
  • some weird operator combos (but valid JS)  may throw off the parser, if that happens, place that code in between curlies, and post a bug report

Hopefully in time, there will be a proper JS parser, but currently the focus is more on the Pascal side, and “asm” sections are intended for handling corner cases more than as a main workhorse.

Tips , , ,

Fixing TCriticalSection

November 30th, 2011

TCriticalSection (along with TMonitor*) suffers from a severe design flaw in which entering/leaving different TCriticalSection instances can end up serializing your threads, and the whole can even end up performing worse than if your threads had been serialized.

This is because it’s a small, dynamically allocated object, so several TCriticalSection instances can end up in the same CPU cache line, and when that happens, you’ll have cache conflicts aplenty between the cores running the threads.

How severe can that be? Well, it depends on how many cores you have, but the more cores you have, the more severe it can get. On a quad core, a bad case of contention can easily result in a 200% slowdown on top of the serialization. And it won’t always be reproducible, since it’s related to dynamic memory allocation.

There is thankfully a simple fix for that, use TFixedCriticalSection:

type
   TFixedCriticalSection = class(TCriticalSection)
      private
         FDummy : array [0..95] of Byte;
   end;

That’s it folks. This makes sure the instance size larger than 96 bytes, which means that it’ll be larger than the cache line in all current CPUs, so no serialization anymore across distinct critical section instances.

As a bonus, it also ends up using one of the larger, more aligned, FastMM bucket, which seems to improve critical section code performance by about 7%. The downside is you use more RAM… but how many critical sections do you really have?

* (11-12-01): as noted by Allen Bauer in the comments, the issue is fixed for TMonitor in XE2.

Tips ,

Don’t publish your .dproj/.groupproj

November 10th, 2011

Just a quick reminder to everyone publishing Delphi projects with source:

Please don’t publish your .dproj & .groupproj, only publish the .dpr & .dpk

The reason? Those files include machine specific settings, such as paths, DCU/DCP/BPL/EXE output directories, along with your favorite debug & release options, which are likely different from that of your fellow developer.

It’s possible to have them manually cleaned up, but that’s tedious and error-prone short of checking their xml content manually.

Pretty much every single project with a .dproj out there has issues: that’s from major open-source projects to Embarcadero’s own samples. None of them (of you) got all of them cleaned up right.

But even getting the published .dproj right doesn’t matter: .dproj is where compile options are stored, options you’re just bound to change and adjust. When those .dproj are in a project you synchronize with via version control (SVN, GIT, etc.), your locally modified .dproj will likely conflict next time you synchronize, sometimes in unintended and not immediately obvious ways.

Hopefully in a future version, Embarcadero will split the .dproj, so that machine-specific settings are in a distinct file from the non-machine specific settings, which would essentially be per-project relative paths to the source files.

Ad interim, .dproj are just a kludge by design.

Tips

Rendering semi-transparent objects in FireMonkey

November 4th, 2011
Comments Off

The question has (predictably) popped up several times now, so here is a recapitulative post with workaround.

FireMonkey (as of now) doesn’t support rendering semi-transparent objects in 3D.

FireMonkey only supports blending of semi-transparent objects (either through the Opacity property or because of their texture, for instance a semi-transparent PNG image), but blending alone is not enough to get it right in 3D with a Z-Buffer (which is what FMX, and most 3D apps are using).

For a technical explanation, you can read about Transparency sorting, the article is about OpenGL, but applies to DirectX too.

A solution (not always quick or simple) is to manually sort the objects back to front, ie. have the objects farthest from the camera be rendered first, using an approach similar to that posted by Peter Söderman:

You should be able to sort them in distance from camera order with something like this (a bit sloppy and you may have to tweak the sorting but it’s a start): [...]

To get access to the children list I do an override of the TViewport3D class.

type
  TMyViewPort = class(TViewport3D)
  end;

procedure TForm1.Button2Click(Sender: TObject);
var
  myv: TMyViewPort;
begin
  myv :=  TMyViewPort( Viewport3D1);

  myv.FChildren.SortList(
  function (i1,i2: Pointer): Integer
  var
    o1,o2: TControl3D;
  begin
    if TfmxObject(i1) is TControl3D then o1 := TControl3D(i1);
    if TfmxObject(i2) is TControl3D then o2 := TControl3D(i2);
    if (o1 <> nil) and (o2 <> nil) then
    begin
      Result := trunc(  VectorDistance2(myv.Camera.Position.Vector,o1.Position.Vector)
                      - VectorDistance2(myv.Camera.Position.Vector,o2.Position.Vector));
    end else
      Result := 0;
  end
  );
end;

You’ll have to call the above sorting every time the objects or camera position changes.

This is just a workaround

Ideally this should be handled by the scene-graph, as it is rendering-dependent, otherwise, as in the code above, you end up having to change the scene-graph structure, which can have various other side-effects, and is even more problematic if you have more than one camera looking at the same scene.

Another downside is that this approach will work with convex objects that don’t intersect, and for which you don’t have triple-overlap.

When intersection or triple overlap happens, there is no object that is fully closer or farther from the camera, and the simple sorting approach fails.

The sorting approach also won’t solve transparency issues that happen for a mesh with itself, for that and the overlap case, you need to involve more advanced techniques that FireMonkey currently doesn’t support, like sorting mesh-sub-element, using tessellation, depth peeling, BSP, etc.

To implement the more advanced techniques mentioned above in a reusable and user-friendly fashion, it would involve standardizing materials an textures, standardizing mesh structures, and generalizing the FMX scene graph, thus hitting the top three architectural weaknesses mentioned previously.

Tips , ,

Delphi XE2 VCL Styles and 3D

October 24th, 2011

…or when the old/new VCL mule shows it can still kick!

I was asked how hard it would be to do yet-another-Cover Flow-clone with VCL+GLScene, and how that would stand vs using FireMonkey on Windows.

GLFlow : VCL + OpenGL

The new Delphi XE2 Styles allow to get a nice looking UI, allowing to mix 3D graphics more smoothly without grayish controls getting in the way:

GLFlow - Powered by Delphi XE2 & GLScene

You can get the source and a pre-compiled executable from googlecode for a more interactive experience or a peek at the source code.

Note that in the video below, image load times have not been edited away: it’s actually near instantaneous (unsurprisingly so, the FireFlow sample pics are small ones)

The pictures above are those from FireMonkey’s “FireFlow”‘ sample, which you’ll already have if you have Delphi XE2 and have updated your samples directory.
You can select any folder with JPG pictures (and compare vs FireFlow), for instance on pictures from a digital camera (like these).

Now for some highlights:

  • High frame-rate
    • even if you shake the slider or click on pictures to the far right/left
    • no need for a gaming 3D card, even Netbook GPUs are enough
  • Fast loading of images in a background thread
    • almost instantaneous for the FireFlow samples
    • you can still interact while the loading takes place
    • works around the old TJPEGImage locking bug (QC43018, QC55871…)
  • Can handle large JPEG images
    • takes advantage of built-in TJPEGImage facilities for quicker loading  & downsizing.
  • Can animate with dozens of pictures without slowdown
    • benefits from built-in frustum culling (doesn’t draw what isn’t visible)
  • No pixel shimmering thanks to anisotropic filtering
    • GPU-accelerated mipmap generation

The amount of code used in GLFlow is actually lower than in the FireFlow sample, despite the extra features. Beyond the loading phase, everything is handled by the GPU, and this performs well even on low-end integrated 3D chipsets (gaming hardware not required, even mere Atom Netbooks can handle it just fine). FireFlow on the other hand seems to require a fast GPU and a fast CPU (it can maximizes one core when animating, maybe an issue in the animation classes?).

Interestingly enough, apart from VCL styles, there is nothing truly “new”: it could have been done almost the same back in Y2K with GLScene and Delphi 5 (if less pretty).

For the cross-platform fans, compiling it for the Mac (with LCL instead of VCL)  is left as an exercise ;-)

I’ve placed the source on google code, as CoverFlow is well, trendy and pretty, and can come in handy. There are also quite a few low-hanging improvements I may improve upon later on (having multiple background loaders, further texture optimizations, cross-platform through LCL, adding text below the pics, etc.).

News, Tips , ,

FireMonkey’s TCube & Tessellation

October 17th, 2011

a cubeIf any of you had a look at FireMonkey’s TCube object, you might have noticed rendering it is quite slow and quite complex.

If you were curious enough to look at the code, you might have noticed that TCube is actually a static mesh made up of 452 vertices, 1440 indices and 480 triangles, instead of the 8 vertices and 6 quads (12 triangles) one could have expected.

That explains why it’s slow… and when looking at the indices, there are more oddities: some coordinates aren’t properly rounded (you can find -1.11759E-8 instead of zero), normals aren’t normalized, and the tessellation doesn’t seem to be the same for all the faces. There is probably an interesting story behind that cube…

That said, why would one want such a complex  cube? Though none of the uses described below apply to TCube, it allows to introduce the subject of tessellation.

Per-Vertex lighting

When lighting an object in 3D, there are two ways to go about it, the historical one (before pixel shaders) was to use per-vertex lighting, with per-pixel linear interpolation, aka “Gouraud shading”. In such a case, if the light is close to the cube, or if the cube is fairly large, you would have artifacts, because the lighting wouldn’t be computed everywhere.

Using GLScene, I’ve illustrated the effect below on a single face (click to enlarge) of the effects of subdividing on per-vertex lighting, you can also download the source + precompiled executable (403 kB) if you want to see it more interactively

Subdivsions and per-vertex-lighting illustration

The closer the light is to a surface, the more subdivisions you need, which means that to get correct lighting with per-vertex lighting, you need a fairly high triangle count. Hence this technique wasn’t too practical, and you usually had to go for tricks (such as using lightmaps, decals, etc.) or vary the subdivision dynamically to minimize the performance impact. F.i. even though the FMX TCube is quite heavy, it’s nowhere near as heavy enough to achieve good per-vertex lighting quality with a close light-source.

Nowadays however, you have hardware pixel shaders, which allow you to compute lighting not per-vertex, but per-pixel, so you can obtain an image similar to the rightmost one above, with only two triangles.

CubeDeformations and vertex shaders

Deformations and vertex shader maths are one first use, like in the Blender picture to the right.

A common mesh manipulation tool, linked to deformation or extrusion tools,is to start from a basic geometric form, with more triangles than strictly necessary, and work on those triangles to achieve a not-basic geometric form.

Another use would be to make a dynamically deforming cube, that would bounce and warp at runtime. For that you would need a vertex shader or CPU-side geometry processing. A variant would be displacement maps, but that usually requires a high number of vertices.

In both those case however, you’ll obviously want to control how much the faces should be subdivided, and in practice, you’re entering the area of controlled mesh tessellation, and leaving that of a simple cube object.

Tips , ,

Memory Manager Investigations

October 13th, 2011

André Mussche on Google+ investigated the performance of several Memory Managers for Delphi, in single-threaded & multi-threaded situations, with detailed results and charts on performance and memory usage. Great work and interesting findings!

His conclusions (which I share)

For single threaded or low memory profile applications, the default Delphi memory manager (FastMM) is the fastest you can get. If you don’t realloc a lot (strings?), TCmalloc [from Google perftools] is fast too.

For multi threaded apps, it’s not easy to decided what to use. ScaleMM2 is the fastest but not stable. TCmalloc is a good one, but uses a lot of memory. MSVCRT [Microsoft allocator in msvcrt.dll] looks scalable in simple multi-threaded tests, but in extended test like FastCodeMMChallenge it is disappointing: slower and uses a lot of memory!
JeMalloc (used by the latest FireFox) is disappointing in multi-threaded areas, but uses the same low memory as FastMM: maybe FF can be made faster by using FastMM? :-)

Additionally, Hoard was tested, though it performed “off the charts” (in a literal and bad way).

You can check André’s charts for yourself:

All in all, for single-threaded applications, or when you have few threads or limited thread-based memory management, FastMM is still king of the Hill, and not just of the Delphi Hill, both in terms of performance, memory usage and robustness.
Pierre le Riche can be proud of his baby ;-)

As for multi-threaded applications, ScaleMM, once stabilized, could well become the next undisputed King of the Hill, and not just of the Delphi Hill again.

I don’t know if Embarcadero are aware of the technical lead this offers to Delphi, this is something worth some marketing buzz and MM authors support surely?

 

 

 

News, Tips , ,

A look at the 3D side of FireMonkey

October 6th, 2011

This post was actually written sometime ago, alas XE2 Update 1 didn’t change much.

I’ve been looking at FireMonkey 3D side, by that I mean strictly the 3D side, not the UI components, or the 2D. Here are some observations, most born from maintaining and developing 3D software in C++ and later with GLScene, and with an eye to eventually porting some of GLScene code to FireMonkey (after all, most of GLScene’s code is actually linear algebra stuff, mesh manipulations, file format imports, etc. and not OpenGL-specific).

Note that everything below is fixable, most of it quite trivially, but Embarcadero has to lead by baking it into the framework, in the core classes & components. If they don’t and you implement it yourself, you’ll end up having to duplicate huge portions of the framework. Such a duplication will be a PITA when they realize they need it in the framework, and implement it… in a fashion that will likely be incompatible with yours.

Also note that implementing some of these will require either interface-breaking changes, still possible now IMHO, as FMX is still young, or hacks/workarounds later on (what happened with GLScene, might as well avoid that if they can).

Scope

FireMonkey is officially pitched at “Business” 3D (as opposed to 3D games), which isn’t that far from what GLScene is used for, as even if GLScene is used for gaming purpose, its bread and butter was as much business as it was gaming (cf. the galleries here and here).

Assuming we’re restricting the scope to real-time rendering engines, what differentiates a game engine for a 3D engine? In terms of pure functionality and capability, there is little specific, Unreal Engine f.i. encompasses a broad range of visualization and UI applications, the most differentiating factor is what the engine processes:

  • A 3D game engine typically sits at the end of an assets tool-chain, and handles “ready to use” meshes, textures, shaders and other assets. The tool-chain is supposed to pre-compile and prepare everything, so that the game engine only has to deal with the rendering and interactivity.
  • A business 3D engine on the other end sits at a higher level, it has to handle raw assets, which come out of simulations, data crunching, image libraries, etc. and do what’s needed to render them in robust, quality fashion, while handling gracefully a variety of situations and corner cases.

In the case of FireMonkey, target platforms are mobile devices, iPad and business machine GPUs: all these are rather low-end hardware, in terms of capability, performance and available memory. In other words, FMX can’t rely on having a powerful GPU with plenty of super-fast video RAM, but rather has to deal with paltry integrated chipsets which share RAM with the CPU.

Scene Graph

Like GLScene, FMX is based on a scene-graph, and more precisely a variant with roots and concepts from the ancient 3DStudio (DOS era), you can see cut down versions of them in FMX: the Camera/Target approach and the Dummy being the most obvious. Similar concepts exist in other scene graphs, but typically with different terminology and slightly different (cf. Ogre, Blender, OSG…).

The FMX scene-graph was however simplified/crippled in several ways:

  • the primary design-time orientation is absolute angles, that’s problematic because rotations aren’t commutative in 3D space, and there are such things as gimbal lock to cause trouble. If relative rotations are practical beyond simple demos, for real world use, absolute angles are not, you need well defined orientation, which means vectors (ie. matrix) or quaternions.
  • the camera model has been over-simplified, leaving out such key aspects as field of view, depth of view and near plane bias. While the first two are key for obvious reasons, the near plane bias is just as important. Because of the maths behind the depth buffer, it is the single most governing factor to numerical accuracy of the depth buffer (and minimize artifacts known as Z-fighting).
  • the scene graph is rendered hierarchically (see below).

The absolute angles orientation existed in GLScene from very early on, and over the years, grew to become a major source of frustration for users, ending up with tutorials dedicated to explaining why they were frustrating, and why you should move away from them.
Unless you’re an airman or accustomed to working in a roll/pitch/turn environment, rotations in 3D just won’t always behave as you expect they should. It was kind of a let down to see this mistake repeatedso prominently in FMX.

The hierarchical scene graph rendering was actually GLScene’s original approach, it’s one that feels quite simple and natural, but it also grew over the years to be a factor holding back the library, and had to be worked/hacked around in different ways. Once again, it was a disappointment to see that FMX was based on GLScene’s old approach.

A better solution is to separate the rendering from the scene graph, this is useful and even required in various scenarios:

  • required to render semi-transparent objects, all techniques for handling opacity require a non-pure -hierarchical rendering
  • facilitates handling of visibility culling, ie. not rendering what isn’t visible, because it’s off-camera, beyond the field of view, occluded by opaque objects, etc.
  • required for deferred shading and other multi-pass approaches (either for shading, shadow volume, etc.)

As a consequence, FireMonkey can’t even render scene graphs containing semi-transparent objects correctly if you don’t manually order them… That’s a major letdown.

Materials and Textures

This is another major roadblock, FMX is using approaches similar to the original GLScene one, that proved problematic and later had to be worked around.

The default material is very limited, and defined into the objects. A better approach (like later GLScene) would be to have them in a material library (which is to materials what an action list is to an action, it allows reuses and centralizes them). With Delphi XE2 property editors, this could have been done in a painless and convenient fashion.

The standard texture model is too limited too, not only are textures not shared (they should live in a library) they also lack basic properties. Sharing textures is key when you don’t have a lot of video memory, or when that memory is slow and not dedicated (like on an iPad, or a business PC). You also need at the very least to be able to control texture wrapping/clamping and texture filtering (mipmap generation and trilinear filtering at the very least).
The lack of mipmapping and anisotropic filtering have implications on performance and rendering quality, the lack of texture built-in texture compression support has performance implications. The COLLADA Viewer demo f.i. exhibits aliasing and pixel shimmering issues because of it.

Provision for 3D textures should also be made, those are almost useless in games, but useful in a business 3D engines (f.i. in medical visualization).

Shaders are for all practical purposes handled as if FMX was a pure game engine: you need pre-compiled shaders, you don’t have a unified cross-platform high-level shader compiler (like Cg) or generator component (writing shaders by hand gets old very fast, as it quickly becomes a combinatorial problem).

All in all, materials aren’t well supported by FMX at design-time, you’re left with having to write your own code to manage material libraries. That just isn’t what you would expect from a general purpose or business 3D engine.

Meshes

Once again, FMX goes for a limited approach similar to that of early GLScene versions: having a specific mesh object for each mesh format, and no standardized mesh format (well there is an embryo of one, but it’s too limited, and the COLLADA viewer skirts it f.i, the 3DS demo before it skirted it too). This is compounded by the scene-graph and materials limitations: the mesh object has to handle its own rendering and its own materials.

This is problematic because it means any form of advanced mesh-based algorithms have to be written against specific mesh objects. This impacts everything mesh-related from imports/exports, manipulations, optimizations, animations (skeletal animations, morphs, etc.) to rendering (extracting silhouettes, BSP, bounding boxes, occlusion etc.) to interaction (collision testing, etc.).

Cadencing

When doing animations, be it a simple following of a spline path or simulations, you need to refresh the display periodically, after having all the scene elements updated.

Typically that involves a so called “game timer”, which triggers at a fixed frequency (usually that of the display, or a fraction of it), along with a frame stepping/progression logic that can handle frame skipping (so that you don’t end up having the UI lag the user when the hardware can’t keep up). You also need a time reference, preferably global and not looping.

Well, FMX only has an embryo of such an architecture, and it is not pervasive. Also the cross-platform time reference (TPlatform.GetTick) returns a single precision floating-point value that can loop… double ouch. Might as well take the opportunity of a new framework to Do It Right.

In GLScene, cadencing wasn’t in initially, and adding it after the fact took time, especially to make it pervasive, pity FMX didn’t build it right into the framework.

Conclusion

All the above points are fixable, but they’re also fundamental missing aspects for doing 3D with FireMonkey, if you don’t want to replicate huge portions of the framework (cf. the COLLADA Viewer sample).

They mean that if you want to achieve anything beyond a few poorly texture objects, you’ll need to design and write a lot of custom code rather than rely on the framework… with obvious implications of obsolescence and compatibility issues whenever FMX finally gets the features in standard.

Tips ,

TdwsRttiConnector: read anything, write anything

October 3rd, 2011

…or to be more accurate, many things the Delphi RTTI can reach ;-)

Raw RTTI connectivity for DWS

The new TdwsRttiConnector components exposes a new RttiVariant type to DWScript, which can be used to dynamically read and write fields/properties, or call methods of any RTTI-exposed Delphi type. You can use it to update or “bind” component properties, with the full power of DWS to draw upon in case of need. For instance:

For instance:

var f := ConnectForm('Form1');
f.Label1.Caption := 'Hello ' + f.Edit1.Text;

with ConnectForm() being a function that allows connecting to any TForm registered in the main TApplication, its implementation looks like

var
   c : TComponent;
begin
   c:=Application.FindComponent(Info.ParamAsString[0]);
   if not (c is TForm) then
      Info.ResultAsVariant:=Null
   else Info.ResultAsVariant:=TdwsRTTIVariant.FromObject(c);
end;

So essentially, to expose any instance as an RttiVariant, you just have to pass the result  TdwsRTTIVariant.FromObject() to a script, be it via as function result, a TdwsUnit-based instance or variable, directly via IInfo, etc. choose your poison.

This offers a very lightweight approach to exposing instances to script, the drawbacks being of course that everything is resolved at runtime (no compile-time checks), and that everything goes through RTTI, which limits performance vs “heavier” forms of class and instance exposure in DWScript, and of course, breaks sandboxing.

Cooked RTTI Connectivity

To get some compile-time checks and leverage strong typing, you can however use the new DWS Connector qualifiers, which look like generics, f.i. if you were to go for a strict version of the above sample:

var f := RttiVariant<Forms.TForm> := ConnectForm('Form1');
var lbl := RttiVariant<StdCtrls.TLabel> := f.Label1;
var edt := RttiVariant<StdCtrls.TEdit> := f.Edit1;
lbl.Caption := 'Hello ' + edt.Text;

In that updated version, the script will be type-checked at compile-time, the only dynamic checks remaining being the binding one (to connect ‘Form1′ by name), but you could choose to remove it by exposing the instance directly to the script. All the rest is type-checked, and if for instance a user mistyped and extra ‘s’:

lbl.Captions := 'Hello ' + edt.Text;

then he would get a compile error about TLabel not having a Captions property. In the unqualified version, that would be a runtime error upon executing the offending line.

This being scripts, it still means you’ll have to type-check at runtime, but you can compile all the scripts present in the application to check for errors, the actual forms and components do not have to be up and running!

News, Tips ,

Taming the Chrome Web Store

September 30th, 2011
Comments Off

Chromium LogoWell, “taming” is probably too ambitious given the jungle that the Chrome Web Store is, especially as this post is restricted to publishing a standalone DWScript/JavaScript app into the Web Store in a few simple steps. Interestingly enough, it seems that publishing Metro apps for Windows 8 will follow a similar process, according to the developer preview.

I’ll use the Flock Demo as an illustration, and turn it into a packaged app, that lives in Chrome and can be access off-line.

Bare minimum requirements

First prepare a folder for the app, in that folder you’ll need all your sources and resources (html, js, images, etc.), in our case, that’s just

  • flock.htm
  • jquery.js

For more complex cases, you are allowed to have sub-folders to neatly arrange everything.

The extra required Chrome package files are, at a bare minimum:

  • manifest.json
  • 16×16 icon (PNG format)
  • 128×128 icon (PNG format)

The manifest.json I used is the following

{
  "name": "DWScript Flock Demo",
  "version": "1.5",
  "description": "Interactive Canvas demonstration for DWScript",
  "app": {
    "launch": {
      "local_path": "flock.htm"
    }
  },
  "icons": {
    "16": "icon_16.png",
    "128": "icon_128.png"
  }
}

Note that the specs for manifest.json are rather “raw”, and if you get something wrong, you’ll likely fail your submission, and each failed attempt, if it’s not caught during upload, will be result in an error at install time. When you re-upload you need to specify a new version number (that’s why it’s at 1.5 in the above snippet!).

Testing and publishing the packaged app

To minimize issues, you can test your app as an unpackaged app in Chrome, go to tools/extensions, then activate the “developer mode”. You’ll then be able to specify the folder in which you placed your app. This also allows to run everything with the developer tools (debugger, etc.).

Once you’re confident enough, you can upload to the Web Store, for that just zip your folder and submit it. The zip can hold sub-folders, but make sure your manifest is at the root of the zip. Be aware there is $5 one-time, lifetime fee per Web Store developer account (not per app).

Then you’ll have the choice of publishing to testers or to end users, that choice is currently final, ie. if you publish to testers, you’ll have to create another web store entry for the user version. Note that there is a small delay between publishing an update, and the update being available for installation.

Finally, all that’s left is a bit of marketing, preparing a screen-shot or two, a YouTube video, a promotional tile, etc. If you’re on a budget, you can use CamStudio to grab the video.

See DWScript Flock Demo on the Chrome Web Store.

As a packaged app, it’ll be available in Chrome from the “Applications” bar of a new tab (Ctrl+T), you can also view them in the context menu.

Tips , ,