Advertisement

Lambda Functions

Started by August 03, 2011 08:11 PM
4 comments, last by _orm_ 13 years, 3 months ago
Something I have been looking into for my project is the implementation of a foreach function that will iterate over objects of a certain type, similar to how the Unreal Engine allows iteration over actors based on their type. This concept is also applied with other parameters such as actors that are colliding, actors that are within a certain proximity, and others. This is built as a construct of the language, and I got to thinking as to how this could be implemented into Angelscript. I already have the ability to iterate objects of a certain type as I am managing all "Actor" types in my engine and am able to sort them out by type. We already have funcdefs, but those only work with global functions. I could use a function object, but that would require me to manage an interface and can prove to be unwieldy in certain situations. So the best option for this to me seems to be anonymous functions, such as C#'s delegates, which allow for anonymous implementation, or Java's anonymous class implementations. Are there any plans for this feature in the future? I could imagine that with the funcdef already implemented, anonymous global functions could probably tie into this somehow. Given the signature of the asFunction passed in, we could, C++ side, determine at runtime whether the function signature has what we require for the function to work. In the following example, I would check the signature of the function's paramaters with the string passed in that denotes the type of object we want to iterate. We could also pass in anonymous functions script side where a funcdef is set as a paramater.

One possible option:

void DoStuff()
{
// Foreach would be registered C++ side.
foreach("ClassType",anon funcdef(ClassType@ obj) {
obj.DoLogicalStuffs();
});
}


I dunno. I am half asleep at the moment, so I may not have worded this post well. I'll come back later this evening and see if I can better explain myself.
I like for-each loops, but I haven't quite figured out how to make this work in a safe way in AngelScript. For example, how do you prevent problems if for example one of the array members are removed in the middle of the for-each loop? And how would you manage more than one for-each loop on the same array/set?

Lambda functions are definitely possible, and hopefully not that difficult to implement now that function pointers work. I'd like to see some examples of what you would like these to look like in the script, and how they would be used. It would help a lot in determining how they might be implemented.

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

Advertisement
Hmm. I think you were editing your post as I responded. I don't remember seeing the example before.

I plan on implementing a generic function handle that will be able to hold any function. This together with a change in the syntax to allow declarations of anonymous functions would make this quite simple to implement.

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 did a bit of thinking and I remembered that when I used XNA and C#, I found the delegate concept
to be extremely simple and effective. I don't think it's necessary to use any special syntax to
implement lambdas. We could just use the normal funcdef syntax.


// We could define this C++ side, with even greater simplifications provided by the future ref addon.
funcdef void ForeachFunction(ref@ a);

// then C++ side define our foreach function as
void foreach(/* whatever extra params are needed, app specific */, ForeachFunction@);


// To use in a script, we could mimic the anonymous delegate syntax from C#

// Angelscript
void DoForeachStuff()
{
// How I would personally be using/abusing it.
foreach("Type",funcdef(ref@ current) // Return type would be implicit and compiler would complain if the funcdef returns but the function doesn't.
{
Type@ actor_type = cast<Type@>(current);

actor_type.DoStuff();
});
}


Another option is to make the anonymous funcdef return type explicit, if it makes the implementation simpler.
Consider if we had a foreach where we want the loop to kick out if the lambda passed in returns false. Implicit
return values work, but can become confusing. I remember that when I was using the C# delegates, which use the syntax
I outlined above but with 'delegate' as the keyword instead of 'funcdef', I started to become confused as to what the
signature to the function should be. I will be honest, I used them almost excessively with my XNA project, but the
convenience they provided when I was making scripted sequences in the game was second only to Java with its anonymous
objects.

// We could define this C++ side, with even greater simplifications provided by the future ref addon.
funcdef bool ConditionalForeachFunction(ref@ a);

// then C++ side define our foreach function as
void foreach(/* whatever extra params are needed, app specific */, ConditionalForeachFunction@);

void DoForeachStuff()
{
// Similar to above only with an explicit return value specified.
foreach("Type",funcdef bool(ref@ current)
{
Type@ actor_type = cast<Type@>(current);
// if Type::DoConditionalStuff returns false, the foreach loop cuts off right there.
return actor_type.DoConditionalStuff();
});
}


This syntax also bears a pretty strong resemblance to C/C++ function pointers and could be easier for C++ developers
to pick up.

typedef bool(*ConditionalForeachFunction)(Actor*);

Applications using the anonymous functions to communicate with the compiled code would need only take a pointer to an
asIScriptFunction.


// This would be about how I would implement this in my game engine's API.
// Not the actual code, but enough to get the idea.
void IRRE_ForeachImpl_Type(const string& type,asIScriptFunction* func)
{
asIScriptContext* ctx = gContextManager->GetInactiveContext();
int function = IRRE_Preparefunction(ctx);

// The list of actors we would be iterating.
vector<Actor*> actor_array;

// The manager would hold a map of vectors to actors each of which holds a core objet from which its type
// is determined.
gObjectManager->PrepareTypeArray(type,actor_array);

vector<Actor*>::iterator i = actor_array.begin();
for( ; i != actor_array.end(); i++ )
{
int result = Execute(ctx,func,(*i));
MACRO_CHECK_EXECUTION(result);
}
}


This could actually even lend itself very well to parallelization through OpenMP or Intel's TBB if your application
can spare the overhead of extra asIScriptContexts floating around.

I do have a question about this implementation though, would it even work? We are basically suspending execution of
the context that called the foreach loop in order to accomplish this, but if we needed to refer to variables outside the
loop, such as the state of an object that is interested in all of the other objects, would they even be accessible? What
do you think?
Except for the syntax for declaring the anonymous function inline like this, what you want to do should already work without a problem.

A function cannot access variables in the calling function (unless received as parameters). Well, you could, if you registered functions that allows the function to access the callstack through the asIScriptContext's debug methods, but that would obviously be a hack.

Maybe I can come up with something that would allow you to declare a function within another function and have that inner function receive dynamic values from the outer function, I believe this has a name already, maybe co-function, or something like that. Anyway, this would probably require an instanciation of a function object everytime the inner function is accessed, so it can get up-to-date values.

Maybe something like this:
funcdef void fptr();
void outer()
{
  int n = 23;

  // The inner function sees the value of the outer function, at the moment it is called
  function void inner() { assert( n == 23 ); }

    // Call the inner function
  inner(); // the assert will work

  // Change the value and call the function
  n = 42;
  inner(); // the assert will fail, since n is now 42
  // Taking a pointer of the inner function will give a pointer to a new instance of the function
  n = 23;
  fptr @func = inner;

  // Changing the value won't affect the function pointer
  n = 42;
  func(); // assert still works, since it remembers the value at the time the pointer was taken
}
It won't be possible for the inner function to update the outer function's values, as that would make it impossible to pass a pointer to the inner function for use somewhere else, since the outer function may not be on the call stack anymore when the inner function is called through the function pointer.

This looks quite accomplishable, and an interesting language feature. I'll keep this in mind for future enhancements.

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


Except for the syntax for declaring the anonymous function inline like this, what you want to do should already work without a problem.

A function cannot access variables in the calling function (unless received as parameters). Well, you could, if you registered functions that allows the function to access the callstack through the asIScriptContext's debug methods, but that would obviously be a hack.

Maybe I can come up with something that would allow you to declare a function within another function and have that inner function receive dynamic values from the outer function, I believe this has a name already, maybe co-function, or something like that. Anyway, this would probably require an instanciation of a function object everytime the inner function is accessed, so it can get up-to-date values.

Maybe something like this:
funcdef void fptr();
void outer()
{
int n = 23;

// The inner function sees the value of the outer function, at the moment it is called
function void inner() { assert( n == 23 ); }

// Call the inner function
inner(); // the assert will work

// Change the value and call the function
n = 42;
inner(); // the assert will fail, since n is now 42
// Taking a pointer of the inner function will give a pointer to a new instance of the function
n = 23;
fptr @func = inner;

// Changing the value won't affect the function pointer
n = 42;
func(); // assert still works, since it remembers the value at the time the pointer was taken
}
It won't be possible for the inner function to update the outer function's values, as that would make it impossible to pass a pointer to the inner function for use somewhere else, since the outer function may not be on the call stack anymore when the inner function is called through the function pointer.

This looks quite accomplishable, and an interesting language feature. I'll keep this in mind for future enhancements.


Well, for passing values I already have a bitstream class I could use that lets me write n number of primitives into a single buffer to read back later. But that seems hackish. I am already thinking about this and one thing I thought of was that we could also use a functor in these situations. Another option is to allow the execution of sub-routines within an active context without having to instantiate another one. For instance:


// C++
void Foreach(string& type,asIScriptFunction* func)
{
asIScriptContext* ctx = asGetActiveContext();
ctx->PrepareForSubroutine();
// Prepare everything you need for the loop.
stuff->Prepare();

// within the loop
ctx->YieldToSubroutine();
ctx->ExecuteSubroutine();
}


The subroutine/coroutine/whateveritscalled could be encapsulated within another struct or possibly within the asIScriptFunction itself.

The basic things I think we would need are these:

1) A way to suspend the current execution (already done).
2) A way to save the current state of the context so that we are able to return to it later.

However, this is all for writing to values stored in the context. Reading the values, however, is just a simple matter of passing them into the foreach function as they are needed. And I personally cant see any reason to write things within an anonymous function.

This topic is closed to new replies.

Advertisement