• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
Sign in to follow this  
Followers 0
InvalidPointer

Delegates in AngelScript

26 posts in this topic

Currently thinking about how delegates should work, as I find myself becoming really, really hamstrung without their inclusion in AngelScript. While I think I have some of the implementation worked out, I'm curious what people would want in an implementation and what the syntax should be. From a technical perspective, I think it's best approached at a single-subscriber level, with a delegate storing a function to call and an additional 'this' pointer; multicast delegates/events could be either a library add-on extending the built-in array type or something left to the application interface to provide.

What I'm not sure of, however, is how this should interact with garbage collection (as I understand it, this bites people in the ass with startling frequency in C#, do we need to have a language-level 'weak reference' construct too?) and cross-module function imports. Community, fire away.
1

Share this post


Link to post
Share on other sites
Ah, you beat me to this subject :)

I feel that delegates is currently one of the most important lacking feature in AngelScript, and one that I was planning to work on for the upcoming releases.

I personally don't have any experience to draw from in designing how delegates should work in AngelScript, so this kind of community input will definitely be important to map out how delegates should work.

Personally I want to make the delegates as simple as possible (e.g. no built-in multicasting). I would also like to see the delegates replace the current function pointers, to reduce the amount of redundant features in the library/language.
0

Share this post


Link to post
Share on other sites
I think the [url="http://en.wikipedia.org/wiki/Delegation_%28programming%29#Method_type_delegation"]C# way[/url] is the best approach as far as language features go. Being able to declare a funcdef / delegate type in-place would be a huge help, as would the ability to take a handle to a member function by storing a "this" pointer if necessary.

It could be a problem if the object that owns the method is destroyed before the delegate, though (I think this is what InvalidPointer is talking about with regards to garbage collection). It probably wouldn't be a good idea to have the delegate keep a strong reference to the object to keep it alive. Maybe there could be a way to invalidate the delegate when its object is destroyed, so calling it would throw an "Unbound function" exception just like calling an empty function handle.

I agree with the decision to keep multicasting out of the engine proper. With the addition of a function call operator it would be reasonably simple to create a custom class that acted as a multicast delegate.
0

Share this post


Link to post
Share on other sites
I made some delegates system in C++ that I exposed to Angel script in my project with minor modifications of a v2.22.0 WIP of AS (in order to be able to specify a method adress).

This delegate system works with events and subscribers, only subscribers has been exposed to my angelscript (ie AS can catch some Engine event but not raise some event).
To expose briefly how it's used:
I register a funcdef in the ASEngine which will be my signature (for instance void MouseMoveEvent(CMouse@, const CVec2f&in))
Then I expose to the script an "event subscriber" (for instance: CMouseMoveEventHandler) that declares a method "SetCallback" which takes in our exemple (IEventHandler@, MouseMoveEvent@). IEventHandler is an empty interface that I declared so AS classes can register and catch events simply by inherit from it (there may be better solutions, and maybe there already is a hidden common parent class to all script classes)

After that all Game Engine that have events expose a RegisterToEventxxx and a UnregisterFromEventxxx (in our exemple RegisterToEventMouseMove(CMouseEventHandler@+) / UnregisterFromEventMouseMove(CMouseEventHandler@+)

Then the class script declares an EventHandler, implements the "callback" method and all is done
(this is pseudo code and may contain syntax errors)
[CODE]
class CScriptCatcher : IEventHandler
{
CScriptCatcher()
{
m_MouseMoved.SetCallback(this, @OnMouseMove);
GetGame().RegisterToEventMouseMove(m_MouseMoved);
}
~CScriptCatcher()
{
GetGame().UnregisterFromEventMouseMove(m_MouseMoved);
}
private void OnMouseMove(CMouse@ _pMouse, const CVec2f& _vDelta)
{
//Do some stuff here to hande mouse move
}
private CMouseMoveEventHandler m_MouseMoved;
}
[/CODE]

There certainly a better way and a better interface to use the idea to be a little more lightweight for the user, but it might help you to have an idea on how things could be done.

For the delegate implementation in C++, it's an event class that holds a list of generics subscribers using templates (because the system is primarily used with C++). Subscribers are only a "this" pointer and method pointer OR a function pointer so delegates can be either method members with a this or a static method / a function.

Hope this might help ;)
0

Share this post


Link to post
Share on other sites
Jake Albano:

I like the C# syntax too (without the multicasting). I think it is the most clean syntax for delegates.



Quittouff:

I'm not sure what customizations you made to the library, but it looks like you don't have a compile time verification of the method signature. Is that right?

There is no common parent class for all script classes, and I do not plan to add one. However, the CScriptHandle add-on would probably work for you as it serves as a generic handle that can hold a reference to any reference object, including script classes.
0

Share this post


Link to post
Share on other sites
The only modifications I made is being able to point a method address: I added a "IsSignatureExceptNameAndObjectEqual" that is the same as "IsSignatureExceptNameEqual" and does not care about the object type, so a funcdef is not dependent on the object type but only on the method signature. It may not be ideal but it was the fastest way for me to do what I wanted.

And actually yes, this method provide a compile time verification of the callback signature, if you implement a callback with the wrong signature, it will not compile at the "SetCallback" as the second argument is a pointer to a funcdef. It would produce this kind of error: (example taken for my script sorry if names are not very relevant)

[CODE]Compiling BPE
MainMenuForm.bhass (3, 5): INFO: Compiling CMainMenuForm::CMainMenuForm()
MainMenuForm.bhass (6, 27): ERROR: No matching signatures to 'CButtonEventHandler::SetCallback(CMainMenuForm&, OnChallengesClick@const)'
MainMenuForm.bhass (6, 27): INFO: Candidates are:
MainMenuForm.bhass (6, 27): INFO: void CButtonEventHandler::SetCallback(IEventHandler@, ButtonEvent@)[/CODE]
(just added an int parameter to the callback method)

So even if the error is not very clear with current implementation, it is triggered when the callback signature is incorrect at compile time.

I didn't think of the CScriptHandle that would have been cleaner indeed. But now the product has been released so it may be for the next one :P.
0

Share this post


Link to post
Share on other sites
It's a good solution for what you wanted.

Delegates would need something similar to work, as they are supposed to be able to take either a global function or an object method. I think the [url="http://www.gamedev.net/topic/626746-function-call-operators-in-the-future/"]opCall feature[/url] that cellulose is working on will be needed too.
0

Share this post


Link to post
Share on other sites
If I may request a feature to be included in this: When taking the handle of an object method, it would be awesome to be able to get accessor functions by their short names. For example:

[CODE]
// This
myDelegate = @myObject.prop;
// Instead of this
myDelegate = @myObject.get_prop;
[/CODE]

I have no experience with writing compilers, so I don't know if this is feasible to implement.
0

Share this post


Link to post
Share on other sites
It's tricky, but can be done similarly to how the property accessors are evaluated already, i.e. the decision to use either set or get needs to be deferred until the very last moment when it can be determined which function signature the delegate is expecting.
1

Share this post


Link to post
Share on other sites
Is Delegate a confirmed feature for AngelScript?

A function pointer and an ad-don just as well do the same job.

[CODE]
void GlobalFunction()
{
}

Object obj1;
Object2 obj2;

Signal signal;

signal << function(Object::DoStuff, @obj1);
signal << function(Object2::DoOtherStuff, @obj2);
signal << function(GlobalFunction);

signal.call(); // call all functions, also accepts parameters and refuses to call functions with wrong parameters.
[/CODE]
I already do this but with strings and cached method pointers.
It just need to be faster with function pointers.

I don't see the point of adding delegates. Or any Observer pattern specific functionality to core language.
0

Share this post


Link to post
Share on other sites
Delegates are confirmed. However, I have no plans on making them as powerful as in C# with multicasting and the likes. AngelScript's delegates will be pretty much the existing funcdef & function handle feature, with the additional support to take a handle to a class method with its corresponding object pointer. Similar to the example you posted.

Multicasting and other features of more advanced languages can be added through add-ons if someone should feel they are necessary.

I'll also add that I haven't quite decided if I'll call the feature delegates, or if I'll simply keep using funcdef.
0

Share this post


Link to post
Share on other sites
That puts me to rest. Simple is the best.

What do you think about reference type of them?
Strong reference on function pointers if just program flow breaking.

I think AngelScript would highly benefit from a weak reference type.
I can think of a thousand ways to utilize them. :)
0

Share this post


Link to post
Share on other sites
It's difficult to make weak references work safely without putting the responsibility in the hands of the script writer. Unless I can figure out a proper solution, weak references are not going to happen.

At least to begin with, the delegate (or function handle) will hold a strong reference to the object. The GC will deal with potential circular references that arises.
0

Share this post


Link to post
Share on other sites
I am sorry :(, i don't understand.

[CODE]
array<funcPtr> events;

class Object
{
Object()
{
events.insertLast(funcPtr(Object::SomeFunc, @this));
}

void SomeFunc() { }
};

void main()
{
Object o;
}
[/CODE]

Above code puts to forces script writer to release reference by hand. If she forgets it will be hell to debug why object aren't released.
But a weak reference releases object and shows a null pointer access warning, which is much easier to spot.

That is not to say all function pointers should be weak references, strong references also have their places.
Maybe a new keyword before objects and function pointers to make them weak pointers.
[CODE]
weak Object @o = Object();
[/CODE]
Probably implementation will be a proxy handle.

I am not experienced with GC languages, probably extending discussion out of my ignorance.
1

Share this post


Link to post
Share on other sites
You're absolutely correct on the benefits on weak references. However, you're only thinking on how they would be used, not how they have to be implemented in the AngelScript engine :)

For the weak reference to become null, so it can give the null pointer exception, it means that the object needs to keep track of all weak references that refer to it, so they can be nulled whenever the object is destroyed. This is not easy to implement in a safe manner, much less with little overhead.
0

Share this post


Link to post
Share on other sites
There is also this very simple implementation:

[CODE]
map<int,void*> handles; // object destructor sets this to null.

class WeakHandle
{
int id;
void *Get()
{
return handles[id].ptr;
}
};

class Function
{
WeakHandle handleToObject;
};
[/CODE]

two improvements would be
- use array to lessen cache misses. WeakHandle needs to hold another integer to do that
- weak references hold count so that GC can clean handles array from nulls. Prevent handles array to keep much unnecessary memory.
- promote script objects to weak objects only when they are referenced from weak pointers. so we don't query handles array for every destructor call.

I have used this system to catch nasty memory leaks, it works wonderfully.
Weak references are much easier to implement than strong ones. That is if the existing system permit this kind of flexibility.

I am not trying to push a new feature on you. Just some ideas for the future of this advanced language.
There is nothing like angelscript on the market.
1

Share this post


Link to post
Share on other sites
I appreciate the suggestions and I'll keep them for later, but it is not quite as simple as you think.

In C++ you can do it like this because you don't have to bother with the co-existance of weak references and strong references. In AngelScript this would just be a tiny part of the solution required to support weak references. I would also not want to incurr the overhead of keeping track of references to all objects, when most of them are not going to have any weak references anyway.

As I said earlier. If I can think of a proper solution I will definitely add it, as I fully agree that they would provide an important benefit to the library.
1

Share this post


Link to post
Share on other sites

Andreas,

 

I have a reference counting class that mixes weak and strong references without having to track references to all objects. I use a form of intrusive reference counting so it may not apply directly to your needs.


struct ref_count_struct
{
	_int strongCount;
	_int weakCount;
};

class reference
{
private:
	ref_count_struct* _ref_count;
public:
	reference()
	{
		_ref_count = new ref_count_struct;

		_ref_count->strongCount = 0;
		_ref_count->weakCount = 0;
	}

	virtual ~reference()
	{
		_ref_count->strongCount = 0;

		if( _ref_count->weakCount == 0)
		{
			delete _ref_count;
			_ref_count = 0;
		}
	}

	inline void ref()
	{
		_ref_count->strongCount++;
	}

	inline void unref()
	{
		// KEEP IT THIS WAY BUG FIX
        if(_ref_count->strongCount == 1)
		{
			delete this;
		}
		else
		{
			_ref_count->strongCount--;
		}
	}

	inline void weakRef()
	{
		_ref_count->weakCount++;
	}

	inline void weakUnref()
	{
		_ref_count->weakCount--;
	}

	inline _int getRefCount() {return _ref_count->strongCount;}
	inline _int getWeakRefCount()
	{
		return _ref_count->weakCount;
	}

	inline ref_count_struct* getRefCountStruct(){return _ref_count;}
};

 

Anyway, the basic idea is to hold the reference counting info in a separate object that sticks around after the object is deleted if and only if the object still has weak references associated with it. Weak_ptrs hold a pointer to the reference counting object so they can check to see if the object has been deleted when access to the object is attempted.

 

 

template<class type> class ref_ptr
{
private:
    type* _ptr;
public:
	
	ref_ptr()
	{
		_ptr = 0;
	}
	
	ref_ptr(type* ptr)
	{
		if(ptr != 0)
		{
			_ptr = ptr;
			_ptr->ref();
		}
		else 
		{
			_ptr = 0;
		}

	}

	ref_ptr(const ref_ptr &ptr)
	{
		if(ptr._ptr != 0)
		{
			_ptr = ptr._ptr;
			_ptr->ref();
		}
		else
		{
			_ptr = 0;
		}
	}
	
	~ref_ptr()
	{
		if(_ptr)
		{
			_ptr->unref();
			_ptr = 0;
		}
	}

	inline type* get()
	{
		return _ptr;
	}
	
	inline type* get() const
	{
		return _ptr;
	}
	
	inline type* operator->()
	{
		return _ptr;
	}
	
	inline type* operator->() const
	{
		return _ptr;
	}
	
        inline type& operator*()
	{
		return *_ptr;
	}
	
	inline type& operator*() const
	{
		return *_ptr;
	}
};

emplate <class type>
class weak_ptr
{
private:
	type* _ptr;
        ref_count_struct* _ref_count;
public:
	
	weak_ptr()
	{
		_ptr = 0;
		_ref_count = 0;
	}
	
	
	weak_ptr(type* ptr)
	{
		if(ptr != 0)
		{
			_ptr = ptr;
			_ref_count = ptr->getRefCountStruct();
			_ptr->weakRef();
		}
                else
                {
			_ptr = 0;
			_ref_count = 0;
                }
	}
	
	weak_ptr(const weak_ptr &ptr)
	{
                if(ptr != 0)
		{
			_ptr = ptr._ptr;
			_ref_count = ptr._ptr->getRefCountStruct();
			_ptr->weakRef();
		}
		else
		{
			_ptr = 0;
			_ref_count = 0;
		}
	}
	
	
	~weak_ptr()
	{
		if(_ref_count != 0)
		{
			// If the referenced object is dead
			if(_ref_count->strongCount == 0)
			{
				// If we aren't the last weak reference
				if( _ref_count->weakCount > 1)
				{
					_ref_count->weakCount--;
					_ref_count = 0;
					_ptr = 0;
				}
				else
				{
					delete _ref_count;
					_ref_count = 0;
					_ptr = 0;
				}
			}
			else
			{
				_ptr->weakUnref();

				_ptr = 0;
				_ref_count = 0;
			}
		}
	}
	
	
	inline type* get()
	{
		if(_ref_count != 0)
		{
			// If the refenced object is dead
			if(_ref_count->strongCount == 0)
			{
				// If we aren't the last weak reference
				if( _ref_count->weakCount > 1)
				{
					_ref_count->weakCount--;
					_ref_count = 0;
					_ptr = 0;
				}
				else
				{
					assert(_ref_count->strongCount == 0 && _ref_count->weakCount <= 1);
					delete _ref_count;

					_ref_count = 0;
					_ptr = 0;
				}
			}
		}

		return _ptr;
	}
	
	inline type* get() const
	{
		if(_ref_count != 0)
		{
			// If the refenced object is dead
			if(_ref_count->strongCount == 0)
			{
				// If we aren't the last weak reference
				if( _ref_count->weakCount > 1)
				{
					_ref_count->weakCount--;
					_ref_count = 0;
					_ptr = 0;
				}
				else
				{
					assert(_ref_count->strongCount == 0 && _ref_count->weakCount <= 1);
					delete _ref_count;

					_ref_count = 0;
					_ptr = 0;
				}
			}
		}

		return _ptr;
	}
	
	inline type* operator->()
	{
		if(_ref_count != 0)
		{
			// If the refenced object is dead
			if(_ref_count->strongCount == 0)
			{
				// If we aren't the last weak reference
				if( _ref_count->weakCount > 1)
				{
					_ref_count->weakCount--;
					_ref_count = 0;
					_ptr = 0;
				}
				else
				{
					assert(_ref_count->strongCount == 0 && _ref_count->weakCount <= 1);
					delete _ref_count;

					_ref_count = 0;
					_ptr = 0;
				}
			}
		}

		return _ptr;
	}
	
	inline type* operator->() const
	{
		if(_ref_count != 0)
		{
			// If the refenced object is dead
			if(_ref_count->strongCount == 0)
			{
				// If we aren't the last weak reference
				if( _ref_count->weakCount > 1)
				{
					_ref_count->weakCount--;
					_ref_count = 0;
					_ptr = 0;
				}
				else
				{
					assert(_ref_count->strongCount == 0 && _ref_count->weakCount <= 1);
					delete _ref_count;

					_ref_count = 0;
					_ptr = 0;
				}
			}
		}

		return _ptr;
	}
	
	inline type operator*()
	{
		if(_ref_count != 0)
		{
			// If the refenced object is dead
			if(_ref_count->strongCount == 0)
			{
				// If we aren't the last weak reference
				if( _ref_count->weakCount > 1)
				{
					_ref_count->weakCount--;
					_ref_count = 0;
					_ptr = 0;
				}
				else
				{
					assert(_ref_count->strongCount == 0 && _ref_count->weakCount <= 1);
					delete _ref_count;

					_ref_count = 0;
					_ptr = 0;
				}
			}
		}

		return *_ptr;
	}
	
	
	inline weak_ptr& operator=(type* ptr)
	{
		if(_ptr == ptr)
			return *this;

		// Handle the old observed object
		if(_ref_count != 0)
		{
			// If the refenced object is dead
			if(_ref_count->strongCount == 0)
			{
				// If we aren't the last weak reference
				if( _ref_count->weakCount > 1)
				{
					_ref_count->weakCount--;
				}
				else
				{
					assert(_ref_count->strongCount == 0 && _ref_count->weakCount <= 1);
					delete _ref_count;
				}
			}
			else
			{
				_ptr->weakUnref();
			}
		}

		if(ptr != 0)
		{
			_ptr = ptr;
			_ref_count = ptr->getRefCountStruct();
			_ptr->weakRef();
		}
		else
		{
			_ptr = 0;
			_ref_count = 0;
		}

		return *this;
	}
};

 

I ripped part of my ref_ptr and weak_ptr class implementations out as examples. These are not complete. I don't know if this will help but I would love to see weak references in AngelScript.

 

- James

Edited by jpmcmu
0

Share this post


Link to post
Share on other sites

Thanks. I actually have something similar in my own game engine. 

 

My game objects are split in two. There is CGameObject that implements all the logic, and there is the CGameObjectLink that provides the weak reference. Each CGameObject has a pointer to the corresponding CGameObjectLink, and whenever the CGameObject needs to be destroyed before all references to it have been removed it simply removes the connection to the the CGameObjectLink. The CGameObjectLink object lives on until all references have been removed, but will no longer forward any calls to CGameObject that has been destroyed.

 

Making this work generically and as transparently as possible in the script language is not easy though, especially when I do not want to incurr a penalty where weak references are not used. 

0

Share this post


Link to post
Share on other sites
I want delegates too, but this is complicated problem. It's maybe too difficult to understand for me.
  1. Holding a pointer of the method does not increase the reference count of the object which contains it?
  2. Now function pointers have both of them, weak reference to a object and the pointer of a function?
  3. How should we implement delegates efficiently?? Would it be difficult to support weak reference in script object class?
Edited by vroad
0

Share this post


Link to post
Share on other sites

1. Correct. Holding a pointer to the method will need to increase the reference count of the object to guarantee the object is alive when the method is actually called. 

2. Yes, the delegate will hold a pointer to both the object and the method. 

3. That is the dilemma. For now I will not bother with weak references, so a delegate will initially hold a hard reference to the object. The garbage collector is already capable of resolving circular references that may arise, so this shouldn't be a major problem.

 

The difficulty with weak references is more a design issue than the actual implementation. The weak references must be generic so the script compiler can build the code for them, but they must also not add a performance penalty where they are not used. I'm toying with several different ideas for this, but so far I'm not ready to start implementing weak references in AngelScript.

Edited by Andreas Jonsson
0

Share this post


Link to post
Share on other sites
but so far I'm ready to start implementing weak references in AngelScript.

 

Was this a typo? It seems you meant not ready.

0

Share this post


Link to post
Share on other sites

Thanks for the contribution. I'll review it carefully when I can and see if it is something I can commit to the svn. 

0

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0