-
Notifications
You must be signed in to change notification settings - Fork 162
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support dynamic GUI Controls #2122
Comments
I have a strong opinion that this problem should be planned in a direction from general case to specific case, that is: first ensure that engine has a potential to create and delete game objects dynamically (any kind of objects!), then design an API for individual elements and solve any remaining situational issues. For this reason, imo, an API for adding controls to GUI is not going to be the first question here. To be honest, I do not understand the argument about GUI controls being special for having a parent to store them, as other objects also have parents that "store" them: Room Objects are "stored" in a Room, and everything else is "stored" in a Game. Similarly to GUI.AddButton(), we may imagine Room.AddObject(), or Game.AddCharacter() (not saying this is the best way to do this, just explaining why I don't see much difference with GUI controls). I was planning to write a document, investigating potential issues for having game objects created dynamically, but keep being distracted... must do this soon. From the top of my head, the save format is going to be an annoying problem here, and save format and process might have to be rethought. Synchronizing game with a save will become a curious issue, not only of engine code, but from a game design perspective. Supposedly, there will have to be a uniform way of allocating all objects, both created at design time and ones created at runtime; so objects described in game data will have to be created from their descriptions in same way, as if they were created in script. I assume that for this reason it would be desired to first have a good refactor of game object classes in the engine, separating design-time descriptions and runtime objects cleanly. (#2058) Having "design-time" objects constructed by a generated script is an interesting alternative, but recreation from description will have to be done either way, because of the room states and game saves. Also it may be convenient to keep the old way at least for a while, because relying on script to generate things may have unexpected limits and consequences in AGS. |
About the save format, currently script objects get saved in whatever amount they need. I also played with plugins and had not too much trouble saving dynamic numbers of arbitrary things there. So I don't fully understand why save format is a problem in this case - I just made a guess from observing the issue in Crystal Shard's game.
There could be a factory call that takes a description of the object and uses it to instantiate the object. Something like the prefab functionality in other engines - although I don't remember what that is exactly, the word sounds like the concept wanted? In box2d there are structs that have no methods you fill with descriptive information and then you pass this struct to a factory method.
I guess the issue is things having to run before game_start and before room_Load. About GUI Controls, they don't go in some array, they have an easy workaround to assign events, they don't support different types of interactions, and they use presentation methods that are hard to emulate other ways (they clip). They also have smaller functionality that is reasonably isolated and well scoped. |
Saving any number of objects is not an issue of course. Another thing that will be a major issue with dynamically created objects, is identification. There are a number of places where objects are referenced by their sequential index in their array. If objects are going to be dynamically created and deleted, then they no longer may be referenced by index, so all of these cases should be changed to use a script name or a pointer, or a unique "key" of another sort. Here I mean both internal refs in engine, and also script API. |
I wonder if i'm overcomplicating this in my head, because I keep thinking about connecting a game designed in editor and a game generated dynamically in script. Perhaps it may help to forget about the Editor completely and imagine what it would look like if engine had everything created in script only. Which class structure the things would have, how things would be stored in it, how they will be updated by the engine, how they would reference each other, and so forth. Then, after having this picture (preferably on paper), try to add Editor and "design-time" data back into this. Anyway, I must take my time to ponder on this topic, because it's been a while since I did last time. |
Speaking of linking functions, currently in AGS the interaction is defined simply as a function name, and nothing else. As these functions are looked up strictly in global script, or current room script for room objects. While this system is kept, the function linking may be simply setting a function name string. |
Please let's not do this, this is prone for failures that aren't picked by the compiler. I REALLY don't think we should go this way. Solving the compiler issues are better and as you said it can be dynamic without those parts for the time being as there are other issues to solve. Please don't pass around strings with close links to script, they are prone for hacked up things that will be hard to debug the issue and forum posts of people mistyping things. Just let not do this NEVER! AGS Script has types and we should use them. There is even #1363 . Everything we do about checking at runtime slows things down! It's better to have everything at compile time nicely so we don't do runtime checks! The thing really bugging me are the IDs, I don't think we should pass them specifically through a dynamic API, or at least I don't think they make sense in this way but I don't know how to reconcile this with the Editor that exposes the IDs of everything. |
Um, I need to clarify, #1363 won't prevent from runtime checks, that's only for the earlier user warning. It won't change anything in the engine. Engine will still have to do these checks to see if a function is present, because it's been told only its name. Furthermore, On another hand, there's no need to overexaggerate slowing down because of these checks. It's not the checks per se that matter, but when and how frequently they are done. Like, in case of room events, room's rep-exec is called 1 time per game frame. Testing that the function exists might be taking a negligible portion of the engine's working time. Still that may be probably optimized, by calculating script address once and saving it, as I mentioned above. Anyway, this is not directly related to the dynamic objects, not sure if it was a good idea to have all this posted at all here. In regards to the runtime function linking. It's important to mention, that it still must be possible to describe the link somehow for serialization, using something that may let find these functions after a save is loaded. |
Meanwhile, I tried to make a "checklist" for implementing dynamic objects. This may be copied somewhere else, like a wiki page, for the later use, because this is related to every type of object, not only gui controls. Dynamic object checklistIdentification String ScriptName is a must have globally unique identifier. There should be a method for finding an object by its script name. Script API If an object is created at runtime, then its script API must fully cover all of its inner properties. Referencing this object from other places in AGS API cannot be done using its global numeric index anymore, as that is not reliable. Lifetime I guess, a standard dynamic object in AGS is one which lifetimes depends on number of references existing. So should be those objects that we turn dynamic. If an object is added to some container, or a link is created between a parent/owner and a child/owned object, then this works as another reference likewise. For example: a gui control added to gui will be referenced by a parent GUI, but also GUI itself will be referenced by each of the controls, so long as they are attached to it. Saves Since the object is dynamic, then the game saves must contain enough data to recreate it from scratch. This basically means that saves should have absolutely all the same data that compiled game data has for this object type. Any part of the object must be possible to describe so that it could be restored reliably. There's a question of restoring references to other things. This is more simple when referencing something saved within same save file. For example, a managed reference may be stored as a internal handle value, because a managed pool is a part of the save, and so these handle values will correspond to objects in pool. But if it references something outside, like assets, or compiled script, then some kind of a key should be used instead, enough to make a lookup and restore a reference after loading this save (if it's still possible). One of the examples is function linking. We cannot save the bare script address for that, because scripts may change since the save was done. Instead we'll have to save something that uniquely identifies the function. Working with the objects in the engine There are two common situations where engine addresses the objects: iteration and remembering an object for later. In case of iteration, there has to be a safety mechanism to prevent errors in case a list of objects was changed in the middle of the process. This may happen for various reasons, like if iteration over objects runs their update, which in turn may trigger a script callback, where user deletes some objects or creates new one. So whatever engine does to an array of objects, if there's a theoretical possibility that this may happen, it should be taken into account. In case of remembering an object, then first of all this should be done using a safe reference kind (not a index in array that may get invalidated too randomly), secondly there also has to be a safety mechanism in case this object gets deleted. In the latter case this reference should perhaps support automatic invalidation, and a validity check. |
Shouldn't this be switched to GUID then? In a dynamic API there is no script name: |
I should have replied earlier, but was wondering how to express my thoughts.
I see what you mean here, when everything is dynamic, then there's no strict correspondence between a pointer variable and "ScriptName" property. In my opinion the ScriptName may have a use, and has certain advantage over guid too in a sense that it's easier to give manually and read by a human too. This makes it useful in debugging and logging, and also in any custom serialization: both when saving an existing game state, and when writing a game scene for the engine to load and generate. ScriptName may still be required for things generated by the Editor, whether these are saved in a game data like now, or created by a generated script. Of course the major question (and doubt) is ensuring its uniqueness. If it's required to be unique, then either constructing a dynamic object may fail with "name not unique" error, or a different name is generated instead. If it's not required to be unique, then the ScriptName may still be used as a kind of a "Tag", but there will be obvious difficulties when using it for any purposes that require unique ID. It's interesting to note that even now there are objects in AGS that are not required to have a ScriptName at all. IIRC these are room objects and hotspots. In regards to the problem of numeric ID. I believe that this goes down to what IDs definition is. If this is a fixed numeric identifier which may be used to reliably find an object, then it may still be useful, as sometimes numbers may be easier to keep than strings. However, if it's a volatile index in some array, where it changes when elements are getting removed or inserted, then it becomes unreliable and difficult or outright impossible to use in practice. Furthermore, given IDs have to be adjusted when the order changes, this may lead to additional performance issues in case of thousands of objects. Right now numeric ID works only because it's fixed, at least within one game version (without recompilations). If it's not fixed then it becomes difficult if not impossible to use. There are unfortunately three cases now even in existing API where object's ID is not fixed. These are: Cameras and Viewport since 3.5.0, and Joysticks in ags4. Same with Joysticks in ags4, currently their IDs are not practical, because it will change whenever some joystick disconnects. Therefore, my opinion is that: there's only a practical reason to have a numeric ID if it's fixed. If it's not fixed (cannot be, or there's no use) then it should not be available at all, and instead be deprecated. |
I am not so sure anymore everything has to be dynamic, if something is guaranteed to exist for the entirety of the game (example: player character, the game main menu, ...) there are a bunch of checks you don't have to do - both in engine but also in the game scripts and in script modules. These may affect performance, but I think the big issue is the surface for bugs. I have a feeling that things created by the Editor in the project tree should get some special treatment and be guaranteed to exist. |
I believe this is more a question of safeguarding and game design rather than a general technical issue. The engine may be made having everything dynamic, but there may be solutions to prevent things from getting deleted by mistake in script. For example, this may be done by tagging generated pointer variables readonly, so that you won't be able to reassign them, and since they will always be assigned to same object, then that object's reference count will always stay positive and they are never deleted. In regards to checks, if there's anything that may make things unstable then the check could be done on assignment once, and either a error thrown, action skipped as forbidden, or a "broken" object flagged as invalid. Again, this is a technical problem that may be solved. |
Describe the problem
Sometimes you may want to make a GUI that has controls tied to some data that changes, and you want to describe it dynamically. A common use-case I have is having a button per savegame slot shown in a grid, which I workaround by pre-creating the necessary buttons - even though I will configure and set them later in script. There is also the workaround of pre-creating controls and using some sort of control pool, but creating controls in big numbers is tedious in the editor.
Suggested change
The idea here is to have only basic buttons and labels that could dynamically be added to a GUI. The api would be something like
Additionally, GUIs would have an additional event that could be linked in the editor for when a control got an event, but it wasn't handled in the control, they would pass to this
unhandled
linked event, along with a pointer for the control (possible since #2061).I believe proper dynamic game entities require something like either #2018 or #1409 so we can pass either an object containing an action or a method. But until we get there, I imagine this could help experiment with other problems that could arise when thinking about dynamic entities - from the top of my head, save files may be an issue. Also if we want to pass a description in
game28.dta
for the things created in the editor or generate an script. I believe a task like this could be useful to experiment with this idea - GUI Controls are particular interesting because they have a parent game element, the GUI, which we can use to store them.I don't expect to be working on this myself in the short time - want to tackle other issues I created in the past - but wanted to throw this idea here to get opinions.
The text was updated successfully, but these errors were encountered: