Advertisement

Delegates in AngelScript

Started by September 17, 2012 05:04 PM
25 comments, last by WitchLord 11 years, 4 months ago
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.

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

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. :)
Advertisement
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.

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 am sorry :(, i don't understand.


array<funcPtr> events;

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

void SomeFunc() { }
};

void main()
{
Object o;
}


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.

weak Object @o = Object();

Probably implementation will be a proxy handle.

I am not experienced with GC languages, probably extending discussion out of my ignorance.
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.

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

There is also this very simple implementation:


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

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

class Function
{
WeakHandle handleToObject;
};


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.
Advertisement
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.

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

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

Sleep is for the weak, or at least that is what I tell myself every morning.

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.

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 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?

This topic is closed to new replies.

Advertisement