Author Topic: Scripting conventions  (Read 3415 times)

0 Members and 1 Guest are viewing this topic.

Offline WMCoolmon

  • Purveyor of space crack
  • 213
Scripting conventions
Code: [Select]
ADE_FUNC(getColor, l_Team, NULL, "Gets the IFF color of the specified Team", "number, number, number", "rgb color for the specified team or nil if invalid") {
    int idx;
    int r,g,b;
    if(!ade_get_args(L, "o", l_Team.Get(&idx)))
        return ADE_RETURN_NIL;

    if(idx < 0 || idx >= Num_iffs)
        return ADE_RETURN_NIL;

    color* col = iff_get_color_by_team(idx, 0, 0);

    r = col->red;
    g = col->green;
    b = col->blue;

    return ade_set_args(L, "iii", r, g, b);
}
Arg, this should really be implemented with a color object. Not that it exists at this point but it would be easy to change the existing functions to have one. Returning a nil is also absolutely unacceptable. The Lua interpreter has a history of irreversibly crashing when it encounters a nil that somebody's treating as something else.

This too:
Code: [Select]
ADE_FUNC(getFOVs, l_Subsystem, NULL, "Returns current turrets FOVs", "number, number, number", "Standard FOV, maximum barrel elevation, turret base fov.")
{
    ship_subsys_h *sso;
    float fov, fov_e, fov_y;
    if(!ade_get_args(L, "o", l_Subsystem.GetPtr(&sso)))
        return ADE_RETURN_NIL;

    if(!sso->IsValid())
        return ADE_RETURN_NIL;

    model_subsystem *tp = sso->ss->system_info;

    fov = tp->turret_fov;
    fov_e = tp->turret_max_fov;
    fov_y = tp->turret_y_fov;

    return ade_set_args(L, "fff", fov, fov_e, fov_y);
}

These are three very different things and should have been implemented as virtvars, if possible. Otherwise you end up implementing a set function as well. So you don't really save anything (two functions implemented instead of three) but clarity enormously suffers at this point. The description doesn't really understand what these things are and if you did fully explain, it would make the description enormous and not easy to read when skimming through the scripting reference.

This three:
Code: [Select]
ADE_FUNC(accessButtonInfo, l_Control_Info, "number, number, number, number", "Access the four bitfields containing the button info", "number, number, number,number", "Four bitfields")
{
    int i;
    int bi_status[4];

    for(i=0;i<4;i++)
        bi_status[i] = 0;

    if(!ade_get_args(L, "|iiii", &bi_status[0], &bi_status[1], &bi_status[2], &bi_status[3]))
        return ADE_RETURN_NIL;

    if(ADE_SETTING_VAR) {
        for(i=0;i<4;i++)
            Player->lua_bi.status[i] = bi_status[i];
    }

    for(i=0;i<4;i++)
        bi_status[i] = Player->lua_bi.status[i];

    return ade_set_args(L, "iiii", bi_status[0], bi_status[1], bi_status[2], bi_status[3]);
}

What bitfields? Once again there is not enough description in the description to be of use to somebody who is not already familiar with what this function is supposed to be doing. Whoever implemented this should have taken a step back and asked themselves whether a completely random modder could understand what this function is for. It looks like it was implemented by somebody with knowledge of the codebase. So this probably has some correspondence to some internal code thing, but is not organized so that somebody outside the looking glass can understand what it's for.

In addition, it's not forward-compatible either. I don't know what the bitfields are for but if they correspond to buttons, what if somebody adds another button to the mouse? Also, I added 'enumerations' specifically to get away from bitfields and make scripting easier to use. I don't think I ever fully implemented them to replace bitfields, but the idea was that you could get very far away from some internal code-dependent thing.

So change to a virtvar array and use enumerations for the bitfields. And improve the name and description. It might even be appropriate to make the separate bits to be separate variables and add/or add an object for them.

Just noticed that this function also sets the four bitfields. Definitely change to a virtvar. This is the whole point that they were implemented.

Four:
Code: [Select]
ADE_FUNC(pollAllButtons, l_Control_Info, NULL, "Access the four bitfields containing the button info", "number, number, number,number", "Four bitfields")
{
    int i;
    int bi_status[4];

    if(!(lua_game_control & LGC_B_POLL_ALL))
        return ADE_RETURN_NIL;

    for(i=0;i<4;i++)
        bi_status[i] = Player->lua_bi_full.status[i];

    return ade_set_args(L, "iiii", bi_status[0], bi_status[1], bi_status[2], bi_status[3]);
}

Every single comment above for this function.

And worst of all:
Code: [Select]
ADE_FUNC(getScreenCoords, l_Vector, NULL, "Gets screen cordinates of a world vector", "number,number", "X (number), Y (number), or false if off-screen")
{
    vec3d v3;
    if(!ade_get_args(L, "o", l_Vector.Get(&v3)))
        return ADE_RETURN_NIL;

    vertex vtx;
    bool do_g3 = G3_count < 1;
    if(do_g3)
        g3_start_frame(1);

    g3_rotate_vertex(&vtx,&v3);
    g3_project_vertex(&vtx);

    if(do_g3)
        g3_end_frame();

    if(vtx.flags & PF_OVERFLOW)
        return ADE_RETURN_FALSE;

    return ade_set_args(L, "ff", vtx.sx, vtx.sy);
}
I think this is a function I coded, and I feel very much at fault for this. First of all, this absolutely breaks my guarantee that I tried to implement the last time I made a huge revision to the scripting system. That guarantee is that you should always be able to treat a function as if it succeeded and not crash. This function should return a point object that is [0,0] in the case of failure but can be checked with a member function.

Not only that but this is probably the first function that had multiple return values, and that is something I wished to avoid as much as possible, even though Lua lets you do this.

I picked these functions out because I saw the multiple return values, saw that this had been converted to SVN, and went "uh-oh". There is nothing wrong with the coding of these functions and I'm sure that they efficiently solved the problem that whoever implemented them was trying to solve. It's just that they seem to be written from the perspective of simply solving that problem, without much consideration for how it's going to affect (i) forwards compatibility (ii) non-insider readability (iii) stability (iv) existing convention.

That last one needs a bit of justification (since I set the convention I'm effectively saying 'because I said so' and I don't feel that I need to hide behind convention, even though that is generally good programming practice). I'm thinking chiefly of virtvars here, and using those whenever a get/set function pair (or an 'access' function) could be used instead. The reasoning was that it makes the syntax simpler and you could do things like
Code: [Select]
mn.Ships['Alpha 1'].Shields[SHIELD_FRONT] = 50As opposed to
Code: [Select]
mn.getShipByName('Alpha 1').accessShields(SHIELD_FRONT, 50)
This is less immeidately obvious as to what you're doing to someone not already familiar with scripting. But the former is familiar to anybody who's ever worked with any procedural programming language before.

But you might argue it would be more intuitive to have get/set:
Code: [Select]
mn.getShipByName('Alpha 1').setShields(SHIELD_FRONT, 50)
This is obvious what the intention is. But if you don't implement a 'get' function, it's not immeidately obvious that set also returns the value of the shields. In fact it would be counter-intuitive to have this:
Code: [Select]
shieldStrength = mn.getShipByName('Alpha 1').setShields(SHIELD_FRONT)
But having a separate 'get' function would force the coder to implement two different functions, and risk things going out of sync.

I apologize for the ginormous message and if you've actually read this far and are still paying attention, I appreciate it. My hope is that some of this explication will draw attention to some of the hidden properties of scripting that aren't immediately obvious but are deliberately there. These are things that I do believe make a scripter's life far more easier in the long-term even if it isn't immediately obvious when only a few functions break the norm.
-C

 

Offline WMCoolmon

  • Purveyor of space crack
  • 213
Re: Scripting conventions
Scripting goals in bullet-point form

Never let the scripter crash the engine, ever. (Always return the same type)

If a function returns an object, it always returns an object. Even if it fails. If it fails, have it return a bogus object that has all the functions and variables as the original object. But similarly, the functions and variables for this false object should also return results that are of the same type as the real object. And so on and so forth.

Use 'ade_set_error' to set these dud values.

Reason:
This prevents modder errors from crashing the interpreter, which isn't easily recovered from. This makes it more likely that if the modder didn't anticipate something, then the game will be able to keep on going rather than crashing back to windows.

If a modder is responsible, then they will check for the return value using isValid().

Example:
If you index mn.Ships with an invalid index, it still returns a dud ship object. If you index that dud ship object's shields, it similarly returns a dud shields object. If you index that shield object, it always returns a number. The only real way to crash it is by dividing by 0. Otherwise what your script will do may be incorrect, but it's not going to be catastrophic.

Forwards compatibility
Writing a scripting function is easy. In some sense that is unfortunate because it is not immediately obvious that this function will be set in stone forever. It is worth the hour of your time to come up with what might happen to change the function you're writing (if it takes that long).

In some sense, writing a scripting function is actually harder than writing code. You can always completely rewrite code as long as it can behave exactly like what you rewrote. With scripting, you can't count on completely rewriting anything.

Reason:
Scripts are intrinsic to a given mod and are included as part of a release. Code isn't.

Hide the implementation as much as possible
The present scripting system is absolutely not meant to be a direct access to the code. The more you expose the way that the code is implemented in scripting, the more you force the code implementation to be the way it is now.

Reason:
Not following this can lead to a really bad situation. Let's posit there's a code subsystem that's really, really badly designed. No coder wants to touch it. But with a little bit of effort, you can extend some critical variable into scripting that will let you do what you want.

Now it's 6 months later, and some coder really wants to rewrite that system completely, make it amazing, run faster, do all kinds of way coooler things, but he can't. Because now scripting depends on the original crappy implementation and if he changes the implementation, none of the scripts that mods have used in the past will work.

In addition, it's probably not user-friendly to write things from the perspective of the way the code is written. Instead you should try to look at it from the perspective of what somebody is trying to do. This will make you tend to look at things from a much higher level, and implement functions that will allow more flexibility.

Example:
drawString does a lot of coding work to actually do what the Lua function does. In some sense it doesn't matter how this function works internally - it could use the usual OpenGL functions or it could use shaders. However the effects of drawString are a good building block for scripts - everybody wants to draw strings.

Make things as clear as possible
Describe clearly and concisely what a function or variable does. Do not assume the user is familiar with scripting or especially the code at all.

This extends conceptually as well. High praise be to s/he who goes the extra mile to implement an object and functions for that object so it works on script in the same way that you think about it.

Reason:
Even an expert scripter will appreciate a description that is tells them everything they need to know from the reference. It cuts down on searching the forums or, even worse, searching the code. Experts use well-written description to save time and annoyance. Novices need well-written descriptions because they're seeing everything from the first time.

Example:
   Looking at the shields on a ship, the way most people will probably be thinking about it is that you're modifying 'front' 'left' 'right' etc. rather than anything to do with the order in which shields are stored in the code.

Stick to convention
This could easily belong with the immediately prior section but it deserves to be its own.  Every time you break convention it creates a special case, an exception. Now anybody who's working with scripting has to remember that one function. Or even worse, three different functions. If you can see a pattern, go for it.

Reason:
Sticking to convention means the scripter only has to memorize one set of rules that applies to everything. Rather than a set of rules for random groups of functions.

Example:
Even though the fs2_open code uses zero-indexed arrays, Lua doesn't. To make things consistent with the internal libraries, FS2Open scripting functions that index in some way should use 1-based indexing. Not doing so can lead to lots of confusion. It may have made no sense to use '1' from a coding perspective, but from a Lua perspective, you can simply assume that all functions/arrays/variables will used 1-based indexing rather than having to keep in mind whether something goes back to a FS2Open function, a Lua function, or maybe even uses both.

And anybody who's had to deal with a lot of functions of completely random capitalization and style will probably get this one. Even when capitalization is strict there are ambiguities.

"getFilename or getFileName" vs "getFilename or getFileName or GetFilename or GetFileName or get_file_name or Get_File_Name or Get_Filename or get_filename or get_Filename or get_FileName"



EDIT:
I should have called this 'Scripting Guidelines' or something like that. I really don't feel like playing the Scripting Police so this is more or less the top things that I feel really need to be a part of scripting for scripting to be powerful yet easy to use. I'm not likely to back down from any of them because each one addresses an important problem, but at the same time there are probably things I would do differently The Next Time.

So these aren't meant to be hard and fast rules to write the functions, at the same time, the set of constraints I've chosen/the problems I chose to solve with scripting do strongly direct things in a certain direction.
« Last Edit: October 29, 2010, 05:35:19 am by WMCoolmon »
-C

 

Offline Nuke

  • Ka-Boom!
  • 212
  • Mutants Worship Me
Re: Scripting conventions
im glad you didnt point out any of my functions when your were bug hunting (though im sure i wrote some bad ones). :D

but yea there have been 3 or 4 coders that have been adding stuff, and many more modders are making use of scripting it seems. ive kinda been taking a break from scripting for the past several months while ive been messing around more with electronics (blame arduino). kinda got me thinking about adding in some kind of inter-process communication or hardware interfaces. i was thinking about named pipes, but someone suggested using a tcp/ip interface, which could make it possible to communicate with both software (through the loop back interface) and hardware out on the network. making such an interface scriptable would be very powerful for anyone who wanted to do mfds or cockpit simulators, or maybe twitter every time you kill a zod.

i added a couple functions to read an objects signature and also access an object by its signature, it has made it a lot easier to implement a metadata system. you just stick all the meta into a table with the signature value as its index. i kinda have been using the convention of accessing read olny values by function, and read/write values by virtvar.

as for nil returns i kinda like them actually, especially when the returns are primitives and not objects. so long as the documentation clearly specifies its fail state. descriptions of functions should list all possible return values and ranges thereof. in the case of getScreenCoords() i can do something like:

Code: [Select]
x,y = somevec:getScreenCoords()
if x then
  --do stuff
end

nil and false are the only 2 logical 0 returns that lua gives us (a value of 0 returns true) for something like getScreenCoords, id like to know if the coords were not on the screen. i may also want to know if the coords fall off the screen and by how much. a false return would suffice as well (and would probibly be better than nil if you dont need both). unless of course there was a 2d vector object that could be invalid under certain situations.  for numbered returns that dont require the full range, i like to see 0 or -1 return on failure. on functions with boolean returns i sometimes like to have a way to differentiate between failure and a successful false return. still anything which would return an object i would still perfer to return an invalid object (which could be checked for validity).

documentation does require some work. i like to fix poorly documented functions in my local code base, and i had been including them with any diffs ive handed over for addition to the engine. pretty much anything that makes me look at the source code to figure out how it works makes me cringe. il usually fix those on the spot. another thin i think needs to be done sorely is to div up the lua.cpp file into more manageable chuncks. the file is huge, and can take a lot of time to navigate.

i still think we need libraries for ai and ui. i thought about making object flag fields accessible with a generic function so that they can be edited. for example i made a weapon that launches a missile which is essentially a ucav drone. while the player can pilot theirs manually i wrote a script to replace the ones spawned by ai with a ship instead so they could have better ai. i kinda wanted to be able to make them kamikaze when they ran out of ammo like was usually done with the player drones. while being able to edit flags would allow this (i think), i would rather have a way to tell a ships ai what to do with itself.

as for ui, i was thinking about doing a piece of lua code which would provide gui elements that scripters could use in the interface or in game. combined with a console like interface where you can use the gui elements to call a command which would do something that the build in interface would normally do, like change display settings, controls, options etc. i had hoped that it would be the foundation for a complete gui upgrade for the game. the coders did not like the idea very much so work on the gui system i was working on has stalled. someone brought up the idea of using the lab interface elements and making them usable anywhere in the engine or scriptable.

also you may notice that we have many more input options available. many of them feel like re-implementations though. i kinda feel like my scripts try to do something the game should already do and effectively it ends up getting done twice. for example i have my own command and binding system in my cockpit script (pretty much all the stuff ive done that will eventuially be included in nukemod if i ever get around to finishing it). right now you have to edit a text file if you want to change your control settings. there are probably 20 new keyboard commands im using right now, some axis commands too.  i might make a binding screen available. but what i want is to be able to use the control options screen that the game already has to set theese custom controls. so when somone plays the mod, they dont ask how to cycle radar so they can use the multitargeting or how to enable the laser guidence system my script allows for. i dont like having to tell people to edit xyz file to bind their controls.

and finally the biggest problem ive had with scripting is organization. a large script can become very hard to navigate fairly quickly. id like to be able to break down mods into chunks to make them easier to work on. it would also allow for modualization of scripts so that you can have libraries that can be dropped right into the scripts folder and used directly. there are also debugging concerns where it can be benificial to track a buck to a certain script file instead of pointing at line 5290 in scripting.tbl. what i kinda want is to be able to do something like:

Code: [Select]
#Global Hooks
$GameInit: [[tbl_parser.lua]]
$GameInit: [[gui.lua]]
$GameInit: [[nukemod_int.lua]]
#end
#Conditional Hooks
$State: GS_STATE_GAME_PLAY
$On Frame: [[of_cockpit.lua]]
$On Frame: [[of_weapons.lua]]
$On Frame: [[of_targeting.lua]]
$Application: FS2_Open

where i can have more than one script file run per hook. all files would be run in the order listed, and any error messages would point to the correct file. ive been using dofile() to run code from a global scripts folder in fs root, where you can keep libraries and frequently used bits of script that might be used in various mods. since do file only knows the game's starting dir and not the local mod dir. this has been somewhat problematic. so ive only been using it for general purpose stuff, and not for script organization (im somone limited to one file per hook). it also kinda not good if the game is to work the way modern operating systems want it to work. with zero user access to the game data folder. a user area needs to be available for script to store user specific files created by the script (like option files).

it probibly sounds like gibberish because im kinda half asleep. but thats kind of what ive been doing lately.
« Last Edit: October 29, 2010, 02:26:10 pm by Nuke »
I can no longer sit back and allow communist infiltration, communist indoctrination, communist subversion, and the international communist conspiracy to sap and impurify all of our precious bodily fluids.

Nuke's Scripting SVN

  

Offline WMCoolmon

  • Purveyor of space crack
  • 213
Re: Scripting conventions
I should be going to sleep too so I'll address some of your points and then try to remember to come back to this.
as for nil returns i kinda like them actually, especially when the returns are primitives and not objects. so long as the documentation clearly specifies its fail state. descriptions of functions should list all possible return values and ranges thereof. in the case of getScreenCoords() i can do something like:

Code: [Select]
x,y = somevec:getScreenCoords()
if x then
  --do stuff
end

nil and false are the only 2 logical 0 returns that lua gives us (a value of 0 returns true) for something like getScreenCoords, id like to know if the coords were not on the screen. i may also want to know if the coords fall off the screen and by how much. a false return would suffice as well (and would probibly be better than nil if you dont need both). unless of course there was a 2d vector object that could be invalid under certain situations.  for numbered returns that dont require the full range, i like to see 0 or -1 return on failure. on functions with boolean returns i sometimes like to have a way to differentiate between failure and a successful false return. still anything which would return an object i would still perfer to return an invalid object (which could be checked for validity).

The emphasis on returning the same type rather than 'nil' is because Lua's method of handling types is because it forces all of the errors into the FS2Open API, rather than making the Lua interpreter deal with them. Since nil is a type by itself there's no way that anything can return anything meaningful of type 'nil'. And since the Lua interpreter has a tendency to explode when you try to do something with a nil, there's no real good way to deal with it that I know of.

If you check every function that can fail, then this isn't a problem. But since practically every function that takes an argument or is part of an object can fail, the cost of error-checking is prohibitively high unless you're familiar with the conditions under which each and every function is going to fail. Even if the documentation enumerated such things, I don't think it would be possible to gain a complete idea without knowing something of the coding on which the function is based.

So the conundrum here is, if something returns a number, how do you check for failure without changing the type. I never considered NaN when doing the scripting system so that may be an option. For boolean functions nil might be OK because IIRC it evaluates to false so every context that it's used in would be identical to the way you'd use a bool, so you wouldn't have any problem except where you did something stupid (like dividing a number by a boolean).

Quote
documentation does require some work. i like to fix poorly documented functions in my local code base, and i had been including them with any diffs ive handed over for addition to the engine. pretty much anything that makes me look at the source code to figure out how it works makes me cringe. il usually fix those on the spot. another thin i think needs to be done sorely is to div up the lua.cpp file into more manageable chuncks. the file is huge, and can take a lot of time to navigate.
That would probably be a very good idea at this point.

Quote
i still think we need libraries for ai and ui. i thought about making object flag fields accessible with a generic function so that they can be edited. for example i made a weapon that launches a missile which is essentially a ucav drone. while the player can pilot theirs manually i wrote a script to replace the ones spawned by ai with a ship instead so they could have better ai. i kinda wanted to be able to make them kamikaze when they ran out of ammo like was usually done with the player drones. while being able to edit flags would allow this (i think), i would rather have a way to tell a ships ai what to do with itself.
AI is probably the hardest thing to move into scripting because of how convoluted and hardcoded it ends up being. As for object flags, I think that is another place where enums would be useful. I can see it one of two ways - either the enums are the flags, or they are indices to an array that represents the flags.

On the other hand a lot of flags are representative of some other mode that might be appropriate to be toggled by a function or virtvar itself.

Quote
as for ui, i was thinking about doing a piece of lua code which would provide gui elements that scripters could use in the interface or in game. combined with a console like interface where you can use the gui elements to call a command which would do something that the build in interface would normally do, like change display settings, controls, options etc. i had hoped that it would be the foundation for a complete gui upgrade for the game. the coders did not like the idea very much so work on the gui system i was working on has stalled. someone brought up the idea of using the lab interface elements and making them usable anywhere in the engine or scriptable.
I guess I'm neutral on this whole thing. I don't think the old interface should be implemented in scripting. But I don't know if it would be worth it to base things off of the lab GUI (Which did have some amount of work put into a skinning system) or make an entirely new GUI and replace the lab one with that.

Quote
also you may notice that we have many more input options available. many of them feel like re-implementations though. i kinda feel like my scripts try to do something the game should already do and effectively it ends up getting done twice. for example i have my own command and binding system in my cockpit script (pretty much all the stuff ive done that will eventuially be included in nukemod if i ever get around to finishing it). right now you have to edit a text file if you want to change your control settings. there are probably 20 new keyboard commands im using right now, some axis commands too.  i might make a binding screen available. but what i want is to be able to use the control options screen that the game already has to set theese custom controls. so when somone plays the mod, they dont ask how to cycle radar so they can use the multitargeting or how to enable the laser guidence system my script allows for. i dont like having to tell people to edit xyz file to bind their controls.
Yeah I think I saw a request to make the $On Key Press take a name rather than a key so it could be linked to something in the options screen. And interfacing with the controls would be nice. That probably has pilot file implications though, so you would need to get taylor in on that.

Quote
and finally the biggest problem ive had with scripting is organization. a large script can become very hard to navigate fairly quickly. id like to be able to break down mods into chunks to make them easier to work on. it would also allow for modualization of scripts so that you can have libraries that can be dropped right into the scripts folder and used directly. there are also debugging concerns where it can be benificial to track a buck to a certain script file instead of pointing at line 5290 in scripting.tbl. what i kinda want is to be able to do something like:

Code: [Select]
#Global Hooks
$GameInit: [[tbl_parser.lua]]
$GameInit: [[gui.lua]]
$GameInit: [[nukemod_int.lua]]
#end
#Conditional Hooks
$State: GS_STATE_GAME_PLAY
$On Frame: [[of_cockpit.lua]]
$On Frame: [[of_weapons.lua]]
$On Frame: [[of_targeting.lua]]
$Application: FS2_Open

where i can have more than one script file run per hook. all files would be run in the order listed, and any error messages would point to the correct file. ive been using dofile() to run code from a global scripts folder in fs root, where you can keep libraries and frequently used bits of script that might be used in various mods. since do file only knows the game's starting dir and not the local mod dir. this has been somewhat problematic. so ive only been using it for general purpose stuff, and not for script organization (im somone limited to one file per hook). it also kinda not good if the game is to work the way modern operating systems want it to work. with zero user access to the game data folder. a user area needs to be available for script to store user specific files created by the script (like option files).

This sounds like there are three things going on here:
(i) Debugging messages are crap
(ii) allowing for scripting libraries
(iii) File access to a userdata area that's appropriate to the operating system

(i) sounds right. I remember there was some issue with getting information to where it needed to be. Like there was no way to get 'proper' debugging messages that I was happy with without some hack.

(ii) I'm not clear what you're saying here

(iii) Yes that is absolutely correct. I'm not sure if the file I/O system could be ported over to the 'correct' way of doing file I/O without breaking scripts, unfortunately.

EDIT:
The new hooks look great. One small thing is that my first thought was that "On Weapon Equipped" is when you first equip the weapon..."On Weapon Active" might have been clearer. But the use of "On" for both instant and repeated actions unfortunately an ambiguity that I didn't realize was there when I wrote the first hooks. :(
« Last Edit: October 30, 2010, 04:25:04 am by WMCoolmon »
-C

 

Offline Nuke

  • Ka-Boom!
  • 212
  • Mutants Worship Me
Re: Scripting conventions
The emphasis on returning the same type rather than 'nil' is because Lua's method of handling types is because it forces all of the errors into the FS2Open API, rather than making the Lua interpreter deal with them. Since nil is a type by itself there's no way that anything can return anything meaningful of type 'nil'. And since the Lua interpreter has a tendency to explode when you try to do something with a nil, there's no real good way to deal with it that I know of.

If you check every function that can fail, then this isn't a problem. But since practically every function that takes an argument or is part of an object can fail, the cost of error-checking is prohibitively high unless you're familiar with the conditions under which each and every function is going to fail. Even if the documentation enumerated such things, I don't think it would be possible to gain a complete idea without knowing something of the coding on which the function is based.

So the conundrum here is, if something returns a number, how do you check for failure without changing the type. I never considered NaN when doing the scripting system so that may be an option. For boolean functions nil might be OK because IIRC it evaluates to false so every context that it's used in would be identical to the way you'd use a bool, so you wouldn't have any problem except where you did something stupid (like dividing a number by a boolean).

i just thought that in situations where you need the full range of the value it might be better to have an extra return parameter to indicate failure or success.
for example, give getScreenCoords() a third return value to tell the script if the function worked or not. so if the value in the first two return types is good the 3rd type would be true, on a failed return it would be false and the number values given could be anything.

Quote
This sounds like there are three things going on here:
(i) Debugging messages are crap
(ii) allowing for scripting libraries
(iii) File access to a userdata area that's appropriate to the operating system

(i) sounds right. I remember there was some issue with getting information to where it needed to be. Like there was no way to get 'proper' debugging messages that I was happy with without some hack.

(ii) I'm not clear what you're saying here

(iii) Yes that is absolutely correct. I'm not sure if the file I/O system could be ported over to the 'correct' way of doing file I/O without breaking scripts, unfortunately.


its not so much that the debugging messages are bad, its more that bugs are difficult to track within a single massive file. mainly i want to be able to take a lua file, and split it into 4 different files, and execute them in order. then when an error pops up it would tell me what file, what line, and any other information to help me figure out whats going on. one of the reasons i started using lua files over putting all the script into scripting.tbl mainly because error messages reported line numbers based on the script block, where line 0 was the first line in the block, which could be line 850 in the file. using lua files makes sure that the line number given by the error message is aligned with the script file. it is also much better organized and easier to work with. but i want to take it a step further and have as many lua files per hook as i may require.

the idea about scripting libraries was mainly to promote plug-in type scripts (kind of like an include file include in c) the script would serve a basic purpose. good example would be my table parser, where i could parse custom tbl files (which could be modded since they use a syntax very similar to most fs tables) and pass the data into script as lua tables. its a piece of code ive used in multiple projects. and its somewhat of a hassel to copy the contents of the parser into another script file. it also makes it somewhat difficult to keep up to date, every time you fix a bug or optimize something you would have to apply it to any scripts you used it in. id keep such scripts in a centralized area, where any script as part of any mod could run them and make use of their features, while only maintaining a single up to date copy. ive been able to accomplish this by using dofile(), which works from the game dir and not necessarily the mod dir. so it allows you to load scripts from a set directory in the freespace folder. unfortunately im not sure of the consequences of this would be in regards to anti cheating measures. and it has been somewhat of a support nightmare since ive had my scripts on svn. i wouldnt mind being able to have a function that could load a script (possibly from a central location) file from any point in the script, execute it in place, and then continue with script execution after where it was called, such a function would need to check the file for modification to prevent exploits.
« Last Edit: October 30, 2010, 06:57:08 am by Nuke »
I can no longer sit back and allow communist infiltration, communist indoctrination, communist subversion, and the international communist conspiracy to sap and impurify all of our precious bodily fluids.

Nuke's Scripting SVN