Jump to content
  • Advertisement
Sign in to follow this  
noizex

Angelscript Features

This topic is 722 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi,

I thought that I will write down my comments on AS that emerged in my head recently instead of making a lot of threads, so here they are. If it makes any difference, my background in such languages comes from LPC language used on MUDs for countless years and modern scripting languages like Pyhon/Ruby. I know AS may never come even close to their flexibility, but there may be few things that could ease the use of it and maybe not require rewriting everything :)

 

Also some things may already be possible, but I don't know about it. I digged through documentation many times but I could miss some things anyway, so if something on the list is already possible (maybe in a bit different way, but still possible) - let me know.

 

I understand some ideas may sound bad to purists - just take into account that I don't know much about AS internals and I'm approaching it more from a user side of view. And I'm coming from a background where there was a thing called "function shadowing" where special object in the virtual world could shadow another, intercepting certain function calls to shadowed object, changing their behaviour. It allowed people to code things no one ever imagined (horse mounts, changing into vampire on runtime, illusions, spell effects and many more), but sounds like terrible programming practice :)

 
1. Enum & const int conflict
 
enum MyEnum {
  Foo = 1,
  Bar
}
 
const int Foo = 2;
 
print(formatInt(Foo)) => 2 
 
So it looks that const int declaration silently masks the enum in the global scope. It still can be refered to as MyEnum::Foo, but
the problem is if we want to use enum in old-C style and it's possible to accidentally mask such enum with const int 
anywhere in the code.
 
Maybe there should be two kinds of enums like typical C enum and "scoped enum" similar to enum class in modern C++, where
you can't refer to enum without prefixing it with the name (MyEnum::Foo). The "old way" would work both ways, so either
Foo or MyEnum::Foo. 
 
If I swap the the order, const int will prevail but without any compile-time warning that we're declaring an enum with 
the same name as existing const. 
 
Summary: It would be good to give some solution for both kinds of uses, either as global-scope const int, or scoped 
more type-safe "enum class". Otherwise for global-scoped const ints it's safer to declare several const ints because 
they won't be possible to override, like:
 
const int Foo = 1;
const int Bar = 2; 
etc.
 
2. Shared types
 
When I compile a type, for example class inheriting from shared type, I still have to #include files where these types are
declared in full. For bigger class hierarchies this may be a lot of code to parse that won't even be compiled (if I'm correct?) 
because these types already exist. Wouldn't it be possible to do some kind of "forward declaration" which tells the compiler 
that these classes are already compiled and shared? Something like:
 
using interface IWeapon;
using mixin MPoisoned;
using class Object;
using class Item;
 
class Weapon: IWeapon, MPoisoned, Object, Item
{
}
 
From what I understand in case of types like interface, class, mixin the name should be unique identifier that should be enough
to find the correct shared type. For functions this would probably require full declaration?
 
Or I'm wrong here and it's not really needed? Somehow it works for C++ registered types and we don't #include any code, so how
can I achieve the same for script types?
 
3. Wrapping/intercepting a function/method call
 
Would it be possible to wrap a call to a function with another function? Or some callback that could be registered and fired when 
calling a function? This would be C++-side functionality but could be used on script side too (like Python decorators for example). 
I see several use cases, from access control (who can and who can't execute function) to real-time masking or wrapping certain 
functions with other functions.
 
4. Auto resolve of return type for functions / C++ registered methods
 
I see there are no "map/filter" functions in Angelscript. These are extremely useful and available in many languages. Lets say
I want a function that will take array argument and execute some anonymous function on every argument, then return such array back.
    
    array<int> foo = { 1, 2, 3}
    auto bar = map(foo, function(e) { return formatInt(e); });  => bar becomes array<string> bar = { "1", "2", "3" }
 
    auto baz = filter(foo, function(e) { return e > 1; }); => baz becomes array<int> baz = { 2, 3 }
    
This means that map/filter function need to take enumerable type like array<T> and return the same array<T> type or different T (in case the type changed during map() call).
 
Such constructs would be extremely convenient for scripting. There is a problem with a strong typing, but the return argument from
map() example could be deducted from anonymous function return argument, the problem is that we can't really register a function
with unknown return type, and decide on it on runtime.
 
5. Implicit casts from "variant" types, like the one used in dictionary
 
Right now I can't just do:
 
    dictionary dict = { { "strval", "foo" }, { "intval", 5 } }
 
    string myval = dict["strval"];
    int myval2 = dict["intval"];
    
because it will throw "Can't implicitly convert from 'dictionaryValue' to X" error. Why, knowing the type of stored value, couldn't 
it actually do this somehow and return string or raise runtime error saying that the type of assignment doesn't match? This seems like
a minor thing, but would be very convenient if it's doable.
 
6. Access mixin through a namespace
 
I wrote about it some time ago when mixins first appeared. It would be nice if we could refer to mixins through their classname, like:
 
   mixin MFood 
   {
       void init();   // initializes internal state for food functionality
       bool eat();
   }
 
   mixin MItem
   {
       void init();   // initializes internal state for item functionality
       bool take();
   }
   
   class Burger: MFood, MItem
   {
        Burger()
        {
            MFood::init();
            MItem::init();
        }
   
        void take()
        {
            if (!paid_for)
            {
                print("You can't take it yet! Pay first.");
                return false;
            }
            return MItem::take();
        }
        
        // bool eat() stays exactly the same as in mixin, Burger doesn't try to define it's own version
   }
 
It would _greatly_ increase the usefullness of mixins, at least in my case. Right now we can't have some initialize function for mixin, 
and have to rename them to init_food() and init_item() - this is not a big deal. The bigger deal is that we can't refer to mixin code once
we override some function. There is a workaround by having 2 methods in mixin, one that has the code to call, and the other that calls the
first function. Then if we override the main function in class inheriting from a mixin, we still have access to that code through the other
method. But using it a lot is troublesome as we're making additional methods just to be able to refer to original mixin method. This could
happen automatically if the inheriting class overrides method of a mixin - if it does, the original mixin method is preserved as a method
with special namespace (Mixin::method for example but it could be even MixinName_method if that's easier to achieve). This way in a case
of method override we'll have access to mixin, but if there is no override - only mixin method remains (though maybe for a sake of coherency it
should be available always through the Mixin:: prefix?)
 
7. &inout for primitive types (int, float?)
 
Is there really no way to make this work? Liks in this code:
 
void apply_modifier(int mod, int& value)
{
    value += mod;
}
 
int value = 5;
for(int i = 0; i < 10; i++)
{
    apply_modifier(i, value);
}
Edited by noizex

Share this post


Link to post
Share on other sites
Advertisement

Hm, for #2 you don't need to #include anything if you are compiling all the scripts in the same module. If that's what you mean?

Share this post


Link to post
Share on other sites

Hm, for #2 you don't need to #include anything if you are compiling all the scripts in the same module. If that's what you mean?

 

No, I'm compiling each script in separate module like: "base_a.as", "base_b.as", "mixin_a.as", "mixin_b.as", "derived_c.as" (inherits from base_a, mixin_a, mixin_b). And for example in derived_c.as I have to do:

 

#include "base_a.as"

#include "mixin_a.as"

#include "mixin_b.as"

 

because it's a new module (my module name is usually path to a file). Of course all classes/mixins here are marked as shared. If I don't include these files it will throw compile error that it can't find BaseA, MixinA and MixinB.

 

The problem is - if there is a lot of mixins to include, or base class is quite big, it may be unecessary parsing for compiler, if all used classes/mixins are guaranted by the engine to be available (and if they're not, we throw error and that's clear sign that the base classes were not compiled correctly). 

Share this post


Link to post
Share on other sites

Thanks for all the feedback. I'll think about these some more when I have more time. But here are some initial thoughts:

 

 

1. enums

 

I agree that the global variable hiding the enum in this case could cause potential problems. I will look into this further to see what can be done about it. Perhaps a warning to inform the writer about it, or even an engine property to allow the application to say that this should produce a name conflict error.

 

2. shared types

 

I already have plans to improve upon how modules can share types and code in a better way. 

 

3. wrapping calls

 

I didn't quite understand the use case here. I have no experience with Python programming though. I'll read up on these Python decorators you mentioned.

 

4. auto resolve

 

This might be solvable through the script templates. At some time I may implement support for template functions too. 

 

5. implicit casts from variant types

 

Yes, this is definitely something that I want to solve. The explicit cast already works for variable types, so it could most likely be extended for use with implicit casts too, though it definitely isn't a trivial change in the compiler to do so.

 

6. mixins

 

I'll look into this. Perhaps I can come up with something

 

7. &inout for primitives

 

You can already use this by turning on asEP_ALLOW_UNSAFE_REFERENCES

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!