Hi everyone! I am porting some parts of Stimulus Control (Stimulus Control v1.0.0) project to CGE. Stimulus Control is a pascal codebase to build software to design, present, analyse and archive behavioral experiments (mainly, with matching-to-sample tasks). I feel that design and archive use-cases fits better to the desktop enviroment. So, my goal is to take advantage of the cross-platform implementation of CGE to present experiments in mobile, web and desktop. The analyse use-case is something really hard to antecipate right now, but I have a feeling that it needs to be web first.
Right now, I am planning some interoperability. I implemented a simple SSDP as a zeroconf prototype (so that different devices can communicate with each other in a local network). I want to send a serialized message from a server computer to a CGE client (mobile, desktop or web) with session configurations (ini and binary). Then, the CGE client will deserialize and consume this configuration, run the session, and will send the result data back to the server when requested to do so.
A session is made of trials. Each trial is a View in CGE. However, I am not completely sure on how to port my current trial life cycle to CGE. Can you help with that? Currently, I hash some strings with classes inside a dictionary. This dictionary is inside a static TTrialFactory that will consume a TCurrentTrial (read from ini and binary config) and create a corresponding TTrial (a view with some logic). Trial ends when a participant’s response happens and a new trial is presented, until the last trial is reached.
Could you shed more light on what is causing you the headache?
For connection related stuff I think you could check the examples [cge_installation_folder/examples/network]. There you have a “tcp_connection” project. You may also be interested in “put_data” project.
CGE can also load remote data easily, check the TCastleZip or TCastleDownload. If some of the data is prepared on the server (e.g. static or semi-static), and used by many clients, you could explore that option.
Then using some kind of a RESTful api can help managing the session progress - as you record their api_key and e.g. a unique_random_key (secret & changing, sent by your server and then returned by the client) you’ll always know which trial has been done, the result, and what to do next.
You say the trial ends with users response. For traditional user input you could overload Press method inside your (*view.pas) to communicate the outcome through the REST. Or use descendants of TCastleControl with events attached.
After a very brief look at your code I see you can use an eye tracker and other stuff - I didn’t dig into it though. You could easily connect the tracker’s events with on-screen controls - cast a ray to determine which control is under the focus point (probably you’d use the SphereCast). For example, you could display 4 TCastleImage to know where the user was looking. Similar goes with other inputs: just hook the event to some method or control, and use the REST api again.
I guess I did not answer your question, nevertheless I hope it helps
Hi @DiggiDoggi , thank you for your insights! I wasn’t thinking about a real time RESTful api. Thank you for that!
My question was really about the trial lifecycle, so the RESTful api solves the data driven events. However, I am not sure how to port the create-free lifecycle. Now, I implemented a LCL inspired polimorphism TCustomControl->TTrial, so each trial is a control. But in CGE I am guessing that each trial must be a TCastleView descendent, so TCastleView->TTrial and then I can do
This polimorfism is at the core of Stimulus Control lifecycle. Is this approach feasible in CGE using TCastleView? Is there another way one should implement this sort of 2D/3D view changing?
You could keep it as TCustomControl, or even better TCastleUserInterface, which is a base class for the UI controls.
I haven’t seen your app in action so I’m kinda guessing what would be the best answer. I understand you have some part of the app that creates the Trial based on received config.
If you define your Trial as
TTrial = class(TCastleUserInterface) // the base class for all TxxxTrials
public
procedure Start; virtual;
procedure Stop; virtual;
end;
Then running the newly received trial (through RESTful) is as simple as
CurrentTrial := CreateTrial(sessionConfig);
// the CreateTrial must be a function that returns correct trial class, based on config
Container.Controls.InsertFront(CurrentTrial);
CurrentTrial.Start;
To stop the existing trial…
if Assigned(CurrentTrial) then
CurrentTrial.Stop;
Container.controls.Remove(CurrentTrial)
FreeAndNil(CurrentTrial)
I think CGE uses FreeAndThenNil, but I don’t know everything about CGE
The functions to receive the config, create the trial, and stopping it can be inside you main unit, or you could use TCastleView (simpler) to display the controls.
In regards to the RESTful, I believe your choice of SSDP was a clever move, I have used it too in some projects. However when you want to go multi-platform you may hit some limits.
For special purposes you could also find ApplicationProperties.FreeDelayed useful. But it’s not something I would recommend as “first way to free objects”, the first way to free objects I would recommend is the regular FreeAndNil (or even relying on TComponent ownership). I would only use FreeDelayed when the situation requires it (and you are fine that freeing is delayed).
Thanks @michalis , I am familiarized with your modern pascal introduction. I am planning to write code in the following style:
program interfaced_free_and_nil;
{$mode ObjFPC}{$H+}
{$INTERFACES CORBA}
uses Classes, SysUtils;
type
IFoo = interface
['{B6D0F8E0-4B6D-4F7A-9E0A-9D7D8E4F4B3A}']
function AsObject: TObject;
end;
TFoo = class(TObject, IFoo)
function AsObject: TObject;
end;
function TFoo.AsObject: TObject;
begin
Result := Self;
end;
var
Foo: IFoo;
begin
if FileExists('heaptrc.txt') then begin
DeleteFile('heaptrc.txt');
end;
{$IF Declared(heaptrc)}
SetHeapTraceOutput('heaptrc.txt');
{$ENDIF}
Foo := TFoo.Create as IFoo;
WriteLn(Foo.AsObject.InstanceSize);
FreeAndNil(Foo.AsObject); // destroy
ReadLn;
end.
Hi everyone, how are you going? Today I am sharing some todos. Feel free to ask, answer questions or comment.
TODO: Port TTrialFactory to TCastleFactoryComponent
Would be great to have a TCastleFactoryComponent to load both design-time .castle-user-interface files and in-memory run-time text based components. The user will have alternatives to design a session. Visually using Castle Game Editor, programatically using an api, or a parametric design using csv tables. All options must output configuration files that will be passed to TTrialFactory.
For example, this code underlines text with a pulsating line:
And this code grows and shrinks a square around something.
TODO: Port SDL2 audio recording to OpenAL audio with Castle Game Engine
Currently, Castle Game Engine have no audio recording capabilities. Also, I need audio device handling, so desktop users will be able to choose from available audio devices. The best is to have no SDL code at all (no extra dependencies, 100% CGE).
TODO: Use either Castle controls or Castle transforms
As far as I am undestanding, controls and transforms are incompatible with each other. I need a common interface to handle the coordinate system and input events with both of them so that people can choose to create session inside or outside viewports. (see next TODO)
TODO: Would be great to have a TCoordinateSystem attached to a TCamera
TCastleCamera is a powerful component. I feel that every ParticipantCamera should have a TTrial as a child.
TODO: Inherit from TComponent or TCastle* visual classes
This way people can take full advantage of the powerful Castle Game Editor and its serialization/deserialization system. Castle Game Editor gracefully follows RAD principles and allows custom design-time components (Custom Components | Manual | Castle Game Engine). It comes to life through *.castle-user-interface JSON files, which are similar to *.lfm, *.dpr and alike.