🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

Upgrade Instructions

Started by
10 comments, last by Deyja 18 years, 11 months ago
I'm about to start a new project, and I'm wondering where the 'how to upgrade from 1.10 to 2.0' instructions went. I've also got a few questions that regard some of the reasons I didn't upgrade in the first place. Can I still pass and return objects by reference in my registered C++ functions? I understand the library now makes many more copies of things passed across the application/script boundary; this isn't a problem as I am already using light weight smart pointers; but what, if anything, must I do in the application function to make sure Angelscripts internal reference counting remains valid? Specifically when passing by reference? Are references still supported in scripts? I understand that handles have replaced pointers in the scripts. I replaced pointers with bound smart pointer types when using 1.10, and never used pointers at all. Is there a way to disable the handle mechanism completly, so script writers can't use it, despite it being safer than pointers? Do global variables still belong to the module, not the context? What kind of changes can I expect to have to make to my bound functions and their registration to facilitate a change from 1.10 to 2.x? Thanks in advance. I don't want to make this rather serious change until I'm sure I know about everything that can jump out and bite me. Incidentally, the new project is a GUI library that will originally be aimed at openGL. The GUI 'widgets' will ultimatly all be the same class inside the application, and their behavior will be controlled by scripts. I'm still pondering the problem of how to store member data on the widgets in a generic fashion, and have a couple of options right now. I could create a unigue module for every widget, meaning data just becomes global variables inside scripts. This has the drawbacks of being somewhat harder to save, and making it impossible to have a true global in a script. In essense, each script would become like the definition of a class - the data is member data, and the functions are methods. Each module would be an instance. This, unfortunatly, means that the script would have to be copied and recompiled for every instance. I could create a generic method for storing types on the object, perhaps using a binding of boost::any. This has the drawbacks of more complicated (and slower) data access; 'identifier' becomes 'thisobj.getMemberAsInt("identifier")', but I can still have 'true globals' and I can also leverage boost::serialize directly for saving. There will only be one copy in memory of each script, but the member data access will involve a tree search using a string key which can cause series performance problems.
Advertisement
Quote: Original post by Deyja
I'm about to start a new project, and I'm wondering where the 'how to upgrade from 1.10 to 2.0' instructions went. I've also got a few questions that regard some of the reasons I didn't upgrade in the first place.


I threw this away, since I didn't think it was good enough. Instead I reorganized the changelog to better show the changes made. Read the changelog carefully and check the manual for other changes that may affect you.

Quote:
Can I still pass and return objects by reference in my registered C++ functions?


Yes, references are still supported. However, when receiving a reference in a parameter to a registered C++ function, do not try to store its pointer as it is only valid for as long as the function is executing. When the function returns the parameter is copied back to the original object (if the parameter was marked with &out).

Quote:
I understand the library now makes many more copies of things passed across the application/script boundary; this isn't a problem as I am already using light weight smart pointers; but what, if anything, must I do in the application function to make sure Angelscripts internal reference counting remains valid?
Specifically when passing by reference?


AngelScript doesn't add anything to the objects, any reference counting done is made using the registered AddRef/Release behaviours. If these behaviours are not registered, then object handles are not available for that type, and AngelScript will treat the type as any other primitive, i.e. make copies of it as necessary.

You just have to make sure the constructor, destructor, and assignment operator. Correctly handles the object's internal reference count.

Quote:
Are references still supported in scripts?


Yes, but references may be copies of the original object. AngelScript may take copies if it has to guarantee the validity of the reference, for example when passing a reference as parameter to a function.

Quote:
I understand that handles have replaced pointers in the scripts. I replaced pointers with bound smart pointer types when using 1.10, and never used pointers at all. Is there a way to disable the handle mechanism completly, so script writers can't use it, despite it being safer than pointers?


Unless you register the AddRef/Release behaviours for a type, the object handles are not available for that type. However, all built-in objects support object handles. If you do not want to support that you'll have to change the library.

If you want to support object handles, you'll need to register AddRef/Release. Release in turn takes care of destroying the object when the reference count reaches zero, so there is no need to register the Destruct behaviour (it will never be called).

Quote:
Do global variables still belong to the module, not the context?


Yes.

Quote:
What kind of changes can I expect to have to make to my bound functions and their registration to facilitate a change from 1.10 to 2.x?


Since you don't use pointers at all, you'll just have to change the declaration of the function parameters that are references. All parameter references must now be marked with a direction keyword, in, out, or inout.

Example: void func(int ∈, int &out)

(Avoid using inout, unless absolutely necessary, since it requires AngelScript to make two copies of the object, once before calling the function and again when the function returns. This in turn requires AngelScript to evaluate the argument expression twice.)

Quote:
Thanks in advance. I don't want to make this rather serious change until I'm sure I know about everything that can jump out and bite me. Incidentally, the new project is a GUI library that will originally be aimed at openGL. The GUI 'widgets' will ultimatly all be the same class inside the application, and their behavior will be controlled by scripts. I'm still pondering the problem of how to store member data on the widgets in a generic fashion, and have a couple of options right now.
I could create a unigue module for every widget, meaning data just becomes global variables inside scripts. This has the drawbacks of being somewhat harder to save, and making it impossible to have a true global in a script. In essense, each script would become like the definition of a class - the data is member data, and the functions are methods. Each module would be an instance. This, unfortunatly, means that the script would have to be copied and recompiled for every instance.
I could create a generic method for storing types on the object, perhaps using a binding of boost::any. This has the drawbacks of more complicated (and slower) data access; 'identifier' becomes 'thisobj.getMemberAsInt("identifier")', but I can still have 'true globals' and I can also leverage boost::serialize directly for saving. There will only be one copy in memory of each script, but the member data access will involve a tree search using a string key which can cause series performance problems.


You may perhaps be able to use the new built-in type any, to store the members. Your application defined class, could have a property any which the scripts can use to store their own data and use as they wish. They can even declare a script structure and store it in the any object. It is possible to serialize the any object, though it currently requires manual iteration of the contents. (This feature will be finished within a couple of week, with the release of 2.3.0)

I wouldn't worry too much about using tree searches with string keys in the GUI system. I doubt that your GUI system will be that much slower because of it. After all, your not planning on implementing the entire game in the GUI system are you? I'm not saying that you should use it, I'm just saying that perhaps you worry too much about the performance in a component where performance isn't so important.

I'm planning on reorganizing the library slightly. Global variables will be local to a context, unless marked as shared in the script. The context will no longer do the actual execution, instead the application creates script threads that execute functions in that context. Global variables may possibly also be marked as thread local, which means that each thread would have it's own copy of the variable. I cannot say when this will be implemented though.

In either case, as you continue to work with AngelScript, I'm sure you will come up with situations where you could use some special feature that AngelScript doesn't have. Make sure to let me know when that is, so that I continue to improve the library. :)

Regards,
Andreas

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Quote: All parameter references must now be marked with a direction keyword, in, out, or inout.


When is this modifier applied? Does it exist only when binding an application function? As I understand it, the in or out is an additional modifier like const, and not the actual variable name, so a full declaration may look like 'f(int ∈ identifier)', correct? This additional behavior really means that one of the advantages of using references is lost, as copies are still made. However, for a reference declared out, the new copy is copied back over the old one, so changes still effect it, correct? Would a reference declared 'in' then act just like it was passed by value, where changes did not stick when returning from the function?

Quote: You may perhaps be able to use the new built-in type any, to store the members. Your application defined class, could have a property any which the scripts can use to store their own data and use as they wish. They can even declare a script structure and store it in the any object. It is possible to serialize the any object, though it currently requires manual iteration of the contents. (This feature will be finished within a couple of week, with the release of 2.3.0)


I would probably have to hack apart your any code to restore type safety. I need strong safety, such that the any can only hold the type it is initialized with ever. Also, it would be nice if there wasn't a visible seam between script-specified members and application-specified members.

Quote: After all, your not planning on implementing the entire game in the GUI system are you?


First, who said it was a game? Second, yes. :D

Quote: Global variables will be local to a context, unless marked as shared in the script.


Great! Would this system lead to an execute function on the ScriptEngine, which received a context as a parameter? Are context's cheap enough memory wise to keep one around for every widget in a nice complex gui? (Say, every widget used to make your browser window right now.)

Quote: I wouldn't worry too much about using tree searches with string keys in the GUI system. I doubt that your GUI system will be that much slower because of it.


Remind me to show you the profiler results for the AngelScript powered mud server I wrote. 70% of execution time was spent in std::map<std::string,std::string>::find. The problem is I think that I have a lot of keys that look like 'longprefix_uniguebit', so it does a lot of uselessly comparing 'longprefix'. If I changed it to 'uniguebit_longpostfix' I would probably see a nice boost in performance.
Quote: Original post by Deyja
When is this modifier applied? Does it exist only when binding an application function? As I understand it, the in or out is an additional modifier like const, and not the actual variable name, so a full declaration may look like 'f(int ∈ identifier)', correct? This additional behavior really means that one of the advantages of using references is lost, as copies are still made. However, for a reference declared out, the new copy is copied back over the old one, so changes still effect it, correct? Would a reference declared 'in' then act just like it was passed by value, where changes did not stick when returning from the function?


You must specify this modifier for all parameter references. And yes, it is a modifier, and not part of the parameter name.

Indeed, this implementation looses some of the advantages with references. Though for behaviour implementations the script library still passes the true object where possible. It can do this because it can assume that the application knows what it is doing with the parameter references. And the parameter references still gives the flexibility of multiple return values, etc.

Yes, a reference marked as 'in' is basically the same thing as passing the argument by value. I keep it to allow compatibility with C++ functions that take arguments by reference or pointer. And yes, the alterations made to reference doesn't reflect on the true object (only if the reference is marked with 'out' or 'inout').

Quote:
I would probably have to hack apart your any code to restore type safety. I need strong safety, such that the any can only hold the type it is initialized with ever. Also, it would be nice if there wasn't a visible seam between script-specified members and application-specified members.


True, any type doesn't provide type safety for the C++ application, though it is type safe for the script language.

If I understand what you mean with a 'visible seam', then I agree that using the any type is currently slightly clumsy. But it works. I'm thinking about someway of allowing the application to register properties, functions, that use script declared structures directly. It will probably use some way of prototyping, e.g. RegisterObjectType("scriptstruct", 0, asOBJ_SCRIPTSTRUCT_PROTOTYPE);

Quote:
First, who said it was a game? Second, yes. :D


Just assuming. After all, most non-game applications aren't too concerned about performance. :)

Quote:
Great! Would this system lead to an execute function on the ScriptEngine, which received a context as a parameter? Are context's cheap enough memory wise to keep one around for every widget in a nice complex gui? (Say, every widget used to make your browser window right now.)


The ExecuteString() can already receive a context from the application.

The current Execute() method will probably be moved from the context to the new thread interface.

Currently contexts are very heavy memory wise, since they contain the script stack. In the future the script stack will be moved to the threads, but contexts will continue to be quite heavy, since they contain all global variables.

Quote:
Remind me to show you the profiler results for the AngelScript powered mud server I wrote. 70% of execution time was spent in std::map<std::string,std::string>::find. The problem is I think that I have a lot of keys that look like 'longprefix_uniguebit', so it does a lot of uselessly comparing 'longprefix'. If I changed it to 'uniguebit_longpostfix' I would probably see a nice boost in performance.


Not, necessarily. I'm not sure about the internal implementation of the std::map, but if it uses a hash of the string as the key, instead of the string itself, it should not matter if most of the strings use the same long prefix. You might experiment with this yourself.

Still, there are structures that are more efficient than a simple map, which is a balanced binary tree. Give hash maps a try, for example.

Regards,
Andreas

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

std::map is implemented using red-black trees or 2-3-4 trees (which are represented as red-black tree), at least in STLPort. It might use a hash value for the nodes though I am uncertain.
std::map relies on operator < to do the ordering. Operator < does not make speed guarantees. Operator < can use a hash for ordering, but not for alphabetical ordering.

What I really want to do is develop a project from start to finish and blog it exstensivly. I've already begun at http://www.omnisu.com/blog/ It has... other design issues first though. I'm really lazy; I have a lot of code laying around that I'd like to just reuse. Most lazy option is to just build it on top of everything I already have. That doesn't quite mesh with the goal of the blog, though. In fact, I might go back and delete what I've done already. The best option is to start completly from scratch. And that's the problem. I don't -want- to go write a vector math library all over again just so I can blog about it. But, I also don't want to just dump code at the reader. It's supposed to be a tutorial. Understand?
Rain Dog:

I'm not sure what the term 2-3-4 tree means. I may know the data structure already, though under a different name. Would you mind giving me a short overview? Red-black trees I'm well versed in, my own asCMap is a Red-Black tree.

Deyja:

Yes, that is what I had understood as well.

If you need both alphabetical order and very fast searches, then maybe it can pay off having two data structures that refer the same objects.

A noble project, your tutorial/blog. I recommend that you go with your idea of reusing your existing code, as it is better to have existing code and explain the necessary parts, than to start everything from scratch just to loose interest before the end.

I am of the opinion that not every bolt and screw of a program needs to be explained in text. For a tutorial to be good, it should explain the design, i.e. why it was written as it was, not show everything that most people know anyway. Too much detail in the text will make it boring to read. Keep the smallest details in the code as comments for those who want to have that detail, if you feel it is necessary as appendices referred to by the text.

Regards,
Andreas

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

http://en.wikipedia.org/wiki/2-3-4_trees

The are a b-tree of order 4. Each node can have 1, 2 or 3 values, with each node also able to have 1-4 children.

in a 2-3-4 tree the left most child is less than the smallest value in the parent node.

the 2nd child is between the 1st and 2nd value of the parent

the 3rd child is between the 2nd and 3rd value of the parent

the 4th child is greater than the 3rd value in the parent.

The red-black tree I believe was designed to simplify the operations in a 2-3-4 tree.
Thanks for the overview, and the link as well.

As I suspected, I had indeed heard of these datastructures before, although under the more generic term, B-trees. Still, I must admit that I didn't remember the term when reading your overview, only when I went to the wiki.



AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

I'll go with what I already have. It gets complicated, though. I'm sometimes excessivly organized. I have a lot of library projects laying around. That's part of why I haven't released the source for my mud server; I'd have to release all these other projects too! I would also have to redistribute AS 1.10 with it, because of how I have it set up in the solution.

This topic is closed to new replies.

Advertisement