#4521728 - 05/21/20 10:41 AM
Re: TARGET - DeferCall help needed
[Re: dmonds]
|
Joined: Jul 2016
Posts: 61
Drakoz
Junior Member
|
Junior Member
Joined: Jul 2016
Posts: 61
|
Is there a way to modify target.tmh to accept functions with 0 arguments?
The first one is easy enough, for me to define my function with a dummy argument.
Yes, that is the best solution. Just put a 0 for the "param" in DeferCall and ignore the parameter in your function. There is little benefit to trying to do otherwise.
...DeferCall(1000, &MyFunc, 0)...
int MyFunc(int x)
{
// your code here, just ignire x
}
How about functions which require 2 arguments?
To do this, pack your parameters into a single variable, or an array and pass an alias to the array. You would then unpack the array or variable as needed in the function. Using a packed variable...
Here is an example packing several numbers into a single int. This technique is used throughout TARGET. When you do PULSE+'a', you are packing the value for PULSE and the ASCII code for the letter 'a' into single integer variable.
From target.tmh...
define PULSE 0x01000000 // a hex value
And 'a' = 0x61 hex, or 97 decimal.
Hence PULSE+'a' = 0x01000061
Here is a code example....
//packedvar is an int that stores several values bit shifted
packedvar = 0x1211 // This has two params, 12 hex and 11 hex, packed as a hex value of 0x1211.
...DeferCall(1000, &MyFunc, packedvar)...
int MyFunc(int parms)
{
x = parms & 0xff // gets lower parameter = 0x11
y = parms >> 8 // gets upper parameter = 0x12
}
The example above is using all normal C syntax. x = parm & 0xff is a mask. ANDing (with the & character) 0xff with parm results in 0x0011 becasue we have masked off the upper 8 bits of 0x1211. parm >> 8 bit shifts the value stored in parm right by 8 bits, which basically shifts the 0x01 off the right side (it is lost) and we are left with 0x02. Or, using an array...
With an array, you can send as many parameters as you want through DeferCall() because you will pass the array's memory address (or its alias using TARGET nomemclature). The array must be defined globally. Otherwise, it will not exist when DeferCall tries to pass it to your function.
// defined globally (outside of main() )
int param1[3] = {10, 11, 12}; // integer array of 3 values
int param2[3] // same, but not preset. Either array type is fine
main()
{
// We will use param2 for this example
param2[0] = 10;
param2[1] = 11;
param2[2] = 12;
... DeferCall(1000, &MyFunc, ¶m2)....
}
int MyFunc(alias params)
{
x = params[0]; // x = 10 (param2[0])
y = params[1]; // y = 11 (param2[1])
z = params[2]; // z = 12 (param2[2])
}
You can also use a struct, or even an array of struct. For example:
// must define the structure and struct variables globally
struct FuncParameters {
int val1;
int val2;
}
FuncParameters myParms;
main {
myParms.val1 = 11;
myParms.val2 = 12;
... DeferCall(1000, &MyFunc, &myParms)....
}
int MyFunc(alias parms)
{
x = parms.val1; // x = 11
y = parms.val2; // y = 12
}
The key to all this is, DeferCall() can only store one parameter, and that parameter must be a number in fact. But since a number can refer to a variable's address (an alias), we can pass an alias to any kind of variable we want, including complex variables like strings, arrays, structures, or arrays of structures. I did not test the above examples, so if you try it and it isn't working, I might have made a mistake. But the concept is correct.
|
|
#4521733 - 05/21/20 11:10 AM
Re: TARGET - DeferCall help needed
[Re: dmonds]
|
Joined: Jul 2016
Posts: 61
Drakoz
Junior Member
|
Junior Member
Joined: Jul 2016
Posts: 61
|
...my second question is...
If I use DeferCall() with a delay of, say 120 seconds, is there a way to cancel this prior to the 120 second timeout?
Before you ask, no, I do not want to be forced to use REXEC.
See RemoveEvent() in hid.tmh as well as tempoproc() in target.tmh for an example of how to use it. Also, look at the definition for DeferCall() in target.tmh as well as AutoRepeat(), StopAutoRepeat(), and rexecproc() in target.tmh. rexecproc() is the function that actually does the REXEC(). All of them use a handle in some form or another which of course we understand as the handle used by REXEC(). It is important to understand that handle is actually used by PostEvent() as EV_USR+1+handle. Knowing that, you can post an event using PostEvent() in order to create a DeferCall(), and then cancel that event using RemoveEvent(). Here is the definition of DeferCall() in target.tmh (I have edited it a little for readability).
int DeferCall(int delay, alias proc, int param)
{
PostEvent(EV_USR, &proc, param, delay);
}
All DeferCall() does is add a call type (EV_USR) to PostEvent(). EV_USR is a handle, like used in REXEC. So you could call PostEvent as follows: PostEvent(EV_USR+1+handle, &MyFunc, params); And then cancel &MyFunc using: RemoveEvent(EV_USR+1+handle); You could probably cancel a DeferCall() function with RemoveEvent(EV_USR), but I am not sure what this will do. It may remove all currently scheduled events? Or just all DeferCalls??? So I would suggest you use the EV_USR+1+handle to create a handle like I explain above, where handle is a number just like you would use for REXEC. I notice that TEMPO uses handles from EV_USR+100 and above as demonstrated in tempoproc() in target.tmh. So looks like it is best to make usre all handles are < 100. For example EV_USR+1+handle, where handle should be < 100. I haven't tested this, but it seems pretty clear this is how TARGET is using these functions. Oh, and it is probably pushing the limit to create an Event and Cancel it within 120ms. TARGET may not be able to create and cancel an even that fast unless your code that creates it is tightly coupled with the code that cancels it.
Last edited by Drakoz; 05/21/20 11:13 AM.
|
|
#4521950 - 05/22/20 04:43 PM
Re: TARGET - DeferCall help needed
[Re: dmonds]
|
Joined: Jul 2016
Posts: 61
Drakoz
Junior Member
|
Junior Member
Joined: Jul 2016
Posts: 61
|
Re answer for 1...Brilliant! I'm glad this is useful. For numbers.
You say "DeferCall() can only store one parameter, and that parameter must be a number in fact" <--- nope, works if I parse a string as well.
Yes, and a string is always passed as an alias. What is an alias in reality? For a string, it is the memory address of the variable which points to the string, which is a number. For an array (say an array of int), it is the memory address of the variable that points to the array. Or to say it more completely, if a = "this is my string", &a is the address of the variable a (an alias), and &&a is the address to the memory location where the string is actually stored. So that is what I meant. This is actually critical to how TARGET works because all the button mapping, EXEC, REXEC, or function calls we store using MapKey(), KeyAxis(), etc. are all stored as integers in a single large array which is called keyalloc[] which is a huge buffer of all this data. Look for keyalloc[16384] in target.tmh for its definition. You don't need to know this to do the stuff I described above, but it helps when trying to answer questions like what you asked. My issue now is that the functions I'm calling that have two arguments typically have one number and one string. That's fine, using a corollary of your theory...I can just merge the integer into the string using sprintf() then diverge in the function.
Yes, that sounds like a good technique. The more advanced (better) way to do it is use a struct like I showed in my last example. Here is kind of the concept, but I am pretty sure this is wrong in a few critical areas because dealing with strings in TARGET is something I still have to think about to get it to work with examples like this. See notes below for those areas I am unsure of.
// must define the structure and struct variables globally
// In C (and TARGET), struct creates a special variable type (like int, float, etc.)
struct FuncParameters {
int myint;
alias mystring;
}
// So we use it like we would use int varname
// Here, we define a variable myParms of variable type FuncParameters
// We access myParms as myParms.myint and myParms.mystring.
FuncParameters myParms;
main {
myParms.myint = 11;
// I'm not sure the following works. Might have to use Map() to do this (which is an advanced topic).
// Look at examles of Map() in target.tmh for details.
&myParms.mystring = "this is my string"; // or use what ever method you need for creating a string like sprintf()
// Or might have to do it like this...
alias s;
&s = "this is my string"; // again use what ever method for creating the string s
myParms.mystring = s; // now we put it in the struct with a simple assignment
// Sorry, I don't have time to play with this more right now, but try it and ask questions.
... DeferCall(1000, &MyFunc, &myParms)....
}
int MyFunc(alias parms)
{
int x;
alias y;
x = parms.myint; // x = 11
&y = &parms.mystring; // y = "this is my string"
}
Again, I didn't test this, so I probably have some mistakes (like how I defined the strings), but hopefully the concept makes sense even if we will have to play with it to figure it out. For example, it might be necessary to use Map() to make the x and y assignments I did above. See examples using Map() in target.tmh, or try it and ask questions. My only gripe with this is that these functions are not just called via a DeferCall but many times as straight calls.....at least it gives me a solution. True, which is why using a struct is the better way to do it. It is easier to pack and unpack things using a struct since the struct defines the packing for you. For more examples of this in TARGET, see the sAxis and sDevice structures in target.tmh which are used to define axdata and devicedata. These variable structs, along with keyalloc[] is where all the magic in TARGET works. Note how these are used in various functions using Map() to access the lower levels of the structures. But I have to apologize as I am pretty sure my example above will not work. I will have to look at this in more detail later.
|
|
#4521951 - 05/22/20 04:50 PM
Re: TARGET - DeferCall help needed
[Re: dmonds]
|
Joined: Jul 2016
Posts: 61
Drakoz
Junior Member
|
Junior Member
Joined: Jul 2016
Posts: 61
|
Re answer for 2...Brilliant! @Drakoz, you sir are a legend!
Apart from knowing this stuff , you actively help people...not just with a solution, but also with learning!
My ulterior motive is, by helping others I learn how to do it myself. :-) Teaching is the best way to learn because it forces you to understand something really well in order to tell someone else how to do it. So it is questions like yours that helped me figure this out all out.
|
|
#4521954 - 05/22/20 04:57 PM
Re: TARGET - DeferCall help needed
[Re: dmonds]
|
Joined: Jul 2016
Posts: 61
Drakoz
Junior Member
|
Junior Member
Joined: Jul 2016
Posts: 61
|
When I define a function;
int fnSomeFunction(int x, int y=0) {}
Assuming that when I call the function via 'fnSomeFunction(a, b);' that 'a' is assigned to the local 'x' variable in the function and that 'b' overrides '0' and is assigned to 'y'.
Can I therefore call the function via 'fnSomeFunction(a);' and assume 'a' is assigned to 'x' and 'y' simply stays at '0' and we won't get a runtime error for not parsing 'y'?
Yes, as you figured out, this is exactly how it works. There are numerous examples of this in target.tmh. TARGET depends on this massively to define variables that the user has not given, or even to define variables that they didn't even document for common functions. For example, look at ActKey() which is defined as follows:
int ActKey(int k, int x=0x7fffffff)
There is nothing in the TARGET documentation about the 2nd variable, x, but they use the x variable regularly inside target.tmh to control how ActKey() works even though such usage is not explained for the user.
|
|
#4522890 - 05/29/20 05:50 AM
Re: TARGET - DeferCall help needed
[Re: dmonds]
|
Joined: Mar 2020
Posts: 12
dmonds
Junior Member
|
Junior Member
Joined: Mar 2020
Posts: 12
Auckland, New Zealand
|
Actually @Drakoz, you have given me a thought about another feature of my script. Two thoughts actually... 1) In Elite Dangerous, the game updates a single line 'status.json' file. This file can have between 3 - 17 key value pairs. As I know the key names, I have already written the code to be able to extract the values by name. The code works, but I consider it somewhat ugly. Perhaps I could use "struct" to more easily extract the key values? sample status.json { "timestamp":"2020-02-06T02:09:41Z", "event":"Status", "Flags":2217263368, "Pips":[4,4,4], "FireGroup":0, "GuiFocus":0, "Fuel":{ "FuelMain":0.000000, "FuelReservoir":0.437468 }, "Cargo":0.000000, "LegalState":"Clean", "Latitude":-39.155632, "Longitude":-165.124420, "Heading":258, "Altitude":0, "BodyName":"HIP 36601 C 1 a", "PlanetRadius":761855.125000 } 2) Next...I recently wrote a function pair to write/read some variables I want to be persistent across script/game restarts. Again, the code is a bit meh, but works. Using struct may be something I could use to better organise and manage this? Here's a sample of the code I use....
define SAVE 0
define LOAD 1
alias MyStatusFile = "d:\\Thrustmaster\\ED_TargetScript\\MyStatusFile.json";
int VoiceVolume = 75; // this can be adjusted on the fly within my script for a voice feedback function I have
int ShipDismissed = 0; // this is just a flag variable I use to track the state of when the ship I'm flying has been 'dismissed' or is still local
int fnsMyStates(int action) {
int fp; // General File Pointer Variable
int fpresult;
int maxbuf = 64;
int buf;
char dummy; Dim(&dummy, maxbuf); dummy[0] = 0;
char GetVar; Dim(&GetVar, 8); GetVar[0] = 0; // Temp variable to extract out individual vars
sprintf(&dummy, "{ \"VoiceVolume\":%03d, \"ShipDismissed\":%d }", VoiceVolume, ShipDismissed); // add leading zeros to VoiceVolume so static length = 3 chars
buf = strlen(&dummy);
strdel(&dummy, buf+1, maxbuf);
char MyVars; Dim(&MyVars, buf+1); MyVars[0] = 0;
if (action == SAVE) {
strcat(&MyVars, &dummy);
fp = fopen(&MyStatusFile, "w"); // creates the file if it doesn't exist
if (fp) {
fpresult = fwrite(&MyVars, 1, buf, fp); // Write MyStates to file
fclose(fp);
}
else {
printf("Error opening file to write: %s\x0a", &MyStatusFile);
}
}
else if (action == LOAD) {
fp = fopen(&MyStatusFile, "r"); // open file to read
if (fp) {
fpresult = fread(&MyVars, 1, buf, fp); // Load MyStates from file
fclose(fp);
strdel(&MyVars, buf, maxbuf); // Garbage removal
if (fpresult > 0) { // Read from file was successful
strsub(&GetVar, &MyVars, 16, 18);
VoiceVolume = ieval(&GetVar); // Extract VoiceVolume
strsub(&GetVar, &MyVars, 37, 38);
ShipDismissed = ieval(&GetVar); // Extract ShipDismissed
printf("\x0a\MyStates successfully loaded\x0a");
}
}
else {
printf("\x0a\!!! Non-Fatal Error opening file to read: %s\x0a", &MyStatusFile);
}
}
else {
printf("Incorrect parameter: fnsMyStates Action = %d\x0a", action);
}
}
So basically, I'm asking, would 'struct' be a better more elegant solution? I know I could spend hours and hours wrapping my head around 'struct' then refactoring my code to suit, so hoping to get a sanity check before potentially wasting my weekend. Cheers dmonds
Last edited by dmonds; 05/29/20 05:54 AM.
|
|
|
CD WOFF
by Britisheh. 03/28/24 08:05 PM
|
|
|
|
|
|
|
|
|
|
|