(C++) second bid at scene node deletion callback

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
garrittg
Posts: 117
Joined: Wed Apr 20, 2005 6:17 pm
Location: Iowa, USA
Contact:

(C++) second bid at scene node deletion callback

Post by garrittg »

heres my second bid at getting a scene node deletion notifier put into base Irrlicht. i still maintain that knowing when a scene node is actually deleted is extremely useful in cleanup of other game/app specific objects related to to the scene node. this method is modeled on on the IEventReceiver setup. there is a new interface called ISceneNodeDeleteCallback. if set on a scene node, it will be called by the deconstructor of the scene node before it releases the internal items such as animators. here is the patch for the current SVN:

Code: Select all

Index: include/irrlicht.h
===================================================================
--- include/irrlicht.h	(revision 609)
+++ include/irrlicht.h	(working copy)
@@ -99,6 +99,7 @@
 #include "IMaterialRendererServices.h"
 #include "ISceneNodeFactory.h"
 #include "ISceneNodeAnimatorFactory.h"
+#include "ISceneNodeDeleteCallback.h"
 #include "ISceneNodeAnimatorCollisionResponse.h"
 #include "IShaderConstantSetCallBack.h"
 #include "IParticleSystemSceneNode.h"
Index: include/ISceneNode.h
===================================================================
--- include/ISceneNode.h	(revision 609)
+++ include/ISceneNode.h	(working copy)
@@ -19,6 +19,7 @@
 #include "irrList.h"
 #include "IAttributes.h"
 #include "IAttributeExchangingObject.h"
+#include "ISceneNodeDeleteCallback.h"
 
 namespace irr
 {
@@ -42,7 +43,7 @@
 			: RelativeTranslation(position), RelativeRotation(rotation), RelativeScale(scale),
 				Parent(parent), ID(id), SceneManager(mgr), TriangleSelector(0),
 				AutomaticCullingState(EAC_BOX), IsVisible(true),
-				DebugDataVisible(EDS_OFF), IsDebugObject(false)
+				DebugDataVisible(EDS_OFF), IsDebugObject(false), deleteCallback(0)
 		{
 			if (Parent)
 				Parent->addChild(this);
@@ -55,6 +56,10 @@
 		//! Destructor
 		virtual ~ISceneNode()
 		{
+			// if there is a delete callback set, call it here
+			if (deleteCallback)
+				deleteCallback->OnDelete(this);
+
 			// delete all children
 			removeAll();
 
@@ -615,6 +620,12 @@
 			return 0; // to be implemented by derived classes
 		}
 
+		//! Sets a new callback to be called when this scene node is deleted
+		void setDeleteCallback(ISceneNodeDeleteCallback* deleteCallback)
+		{
+			this->deleteCallback=deleteCallback;
+		}
+
 	protected:
 
 		//! this method can be used by clone() implementations of derived classes
@@ -701,6 +712,8 @@
 
 		//! is debug object?
 		bool IsDebugObject;
+		// pointer to delete callback
+		ISceneNodeDeleteCallback* deleteCallback;
 	};
 
 } // end namespace scene
Index: include/ISceneNodeDeleteCallback.h
===================================================================
--- include/ISceneNodeDeleteCallback.h	(revision 0)
+++ include/ISceneNodeDeleteCallback.h	(revision 0)
@@ -0,0 +1,31 @@
+// Copyright (C) 2002-2006 Nikolaus Gebhardt
+// This file is part of the "Irrlicht Engine".
+// For conditions of distribution and use, see copyright notice in irrlicht.h
+
+#ifndef __I_SCENENODE_DELETE_CALLBACK_H_INCLUDED__
+#define __I_SCENENODE_DELETE_CALLBACK_H_INCLUDED__
+
+namespace irr
+{
+namespace scene
+{
+	class ISceneNode;
+
+	class ISceneNodeDeleteCallback
+	{
+	public:
+
+		//! destructor
+		virtual ~ISceneNodeDeleteCallback() {}
+
+		/// <summary>
+		/// Called right before an ISceneNode is deleted
+		/// </summary>
+		/// \param node: ISceneNode being deleted
+        virtual void OnDelete(ISceneNode* node) = 0;
+	};
+} // end namespace scene
+} // end namespace irr
+
+#endif
+
and here is a modified version of the movement example showing its use. during the example, press 'D' to remove the test nodes.

Code: Select all

/*
This Tutorial shows how to move and animate SceneNodes. The
basic concept of SceneNodeAnimators is shown as well as manual
movement of nodes using the keyboard.

As always, I include the header files, use the irr namespace,
and tell the linker to link with the .lib file.
*/
#include <irrlicht.h>
#include <iostream>

using namespace irr;

#pragma comment(lib, "Irrlicht.lib")

/*
In this tutorial, one of our goals is to move a scene node using some
keys on the keyboard. We store a pointer to the scene node we want to
move with the keys here.
The other pointer is a pointer to the Irrlicht Device, which we need
int the EventReceiver to manipulate the scene node and to get the
active camera.
*/

scene::ISceneNode* node = 0;
IrrlichtDevice* device = 0;

scene::ISceneNode* deletenodes[5];
s32 nodeCnt=5;

/*
define an ondelete handler
*/
class MyNodeDeleteNotifier : public scene::ISceneNodeDeleteCallback
{
public:
	virtual void OnDelete(scene::ISceneNode* node)
	{
        printf("node %d was deleted\n",node->getID());
	}
};

/*
To get events like mouse and keyboard input, or GUI events like
"the OK button has been clicked", we need an object wich is derived from the
IEventReceiver object. There is only one method to override: OnEvent.
This method will be called by the engine when an event happened.
We will use this input to move the scene node with the keys W and S.
*/
class MyEventReceiver : public IEventReceiver
{
public:
	virtual bool OnEvent(SEvent event)
	{
		/*
		If the key 'W' or 'S' was left up, we get the position of the scene node,
		and modify the Y coordinate a little bit. So if you press 'W', the node
		moves up, and if you press 'S' it moves down.
		*/

		if (node != 0 && event.EventType == irr::EET_KEY_INPUT_EVENT&&
			!event.KeyInput.PressedDown)
		{
			switch(event.KeyInput.Key)
			{
			case KEY_KEY_D:
          		if (nodeCnt>0) {
                    deletenodes[nodeCnt-1]->remove();
                    nodeCnt--;
          		}
                break;
			case KEY_KEY_W:
			case KEY_KEY_S:
				{
					core::vector3df v = node->getPosition();
					v.Y += event.KeyInput.Key == KEY_KEY_W ? 2.0f : -2.0f;
					node->setPosition(v);
				}
				return true;
			}
		}

		return false;
	}
};


/*
The event receiver for moving a scene node is ready. So lets just create
an Irrlicht Device and the scene node we want to move. We also create some
other additional scene nodes, to show that there are also some different
possibilities to move and animate scene nodes.
*/
int main()
{
	// let user select driver type

	video::E_DRIVER_TYPE driverType = video::EDT_DIRECT3D9;

	printf("Please select the driver you want for this example:\n"\
		" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
		" (d) Software Renderer\n (e) Burning's Software Renderer\n"\
		" (f) NullDevice\n (otherKey) exit\n\n");

	char i;
	std::cin >> i;

	switch(i)
	{
		case 'a': driverType = video::EDT_DIRECT3D9;break;
		case 'b': driverType = video::EDT_DIRECT3D8;break;
		case 'c': driverType = video::EDT_OPENGL;   break;
		case 'd': driverType = video::EDT_SOFTWARE; break;
		case 'e': driverType = video::EDT_BURNINGSVIDEO;break;
		case 'f': driverType = video::EDT_NULL;     break;
		default: return 0;
	}

	// create device
	MyEventReceiver receiver;

	IrrlichtDevice* device = createDevice( driverType, core::dimension2d<s32>(640, 480),
		16, false, false, false, &receiver);

	if (device == 0)
		return 1; // could not create selected driver.


	video::IVideoDriver* driver = device->getVideoDriver();
	scene::ISceneManager* smgr = device->getSceneManager();


	/*
	Create the node for moving it with the 'W' and 'S' key. We create a
	sphere node, which is a built in geometry primitive. We place the node
	at (0,0,30) and assign a texture to it to let it look a little bit more
	interesting. Because we have no dynamic lights in this scene we disable
	lighting for each model (otherwise the models would be black).
	*/
	node = smgr->addSphereSceneNode();
	node->setPosition(core::vector3df(0,0,30));
	node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
	node->setMaterialFlag(video::EMF_LIGHTING, false);


	/*
	Now we create another node, moving using a scene node animator. Scene node
	animators modify scene nodes and can be attached to any scene node like
	mesh scene nodes, billboards, lights and even camera scene nodes. Scene node
	animators are not only able to modify the position of a scene node, they can
	also animate the textures of an object for example.
	We create a cube scene node and attach a 'fly circle' scene node to it, letting
	this node fly around our sphere scene node.
	*/
	scene::ISceneNode* n = smgr->addCubeSceneNode();

	if (n)
	{
		n->setMaterialTexture(0, driver->getTexture("../../media/t351sml.jpg"));
		n->setMaterialFlag(video::EMF_LIGHTING, false);
		scene::ISceneNodeAnimator* anim =
			smgr->createFlyCircleAnimator(core::vector3df(0,0,30), 20.0f);
		n->addAnimator(anim);
		anim->drop();
	}

	/*
	The last scene node we add to show possibilities of scene node animators is
	a md2 model, which uses a 'fly straight' animator to run between to points.
	*/
	scene::IAnimatedMeshSceneNode* anms = smgr->addAnimatedMeshSceneNode(smgr->getMesh("../../media/sydney.md2"));

	if (anms)
	{
		scene::ISceneNodeAnimator* anim =
			smgr->createFlyStraightAnimator(core::vector3df(100,0,60),
			core::vector3df(-100,0,60), 2500, true);
		anms->addAnimator(anim);
		anim->drop();

		/*
		To make to model look right we set the frames between which the animation
		should loop, rotate the model around 180 degrees, and adjust the animation speed
		and the texture.
		To set the right animation (frames and speed), we would also be able to just
		call "anms->setMD2Animation(scene::EMAT_RUN)" for the 'run' animation
		instead of "setFrameLoop" and "setAnimationSpeed",
		but this only works with MD2 animations, and so you know how to start other animations.
		but it a good advice to use not hardcoded frame-numbers...
		*/
		anms->setMaterialFlag(video::EMF_LIGHTING, false);

		anms->setFrameLoop(160, 183);
		anms->setAnimationSpeed(40);
		anms->setMD2Animation(scene::EMAT_RUN);

		anms->setRotation(core::vector3df(0,180.0f,0));
		anms->setMaterialTexture(0, driver->getTexture("../../media/sydney.bmp"));

	}


	/*
	To be able to look at and move around in this scene,
	we create a first person shooter style camera and make the
	mouse cursor invisible.
	*/
	scene::ICameraSceneNode * cam = smgr->addCameraSceneNodeFPS(0, 100.0f, 100.0f);
	device->getCursorControl()->setVisible(false);

	/*
	Add a colorful irrlicht logo
	*/
	device->getGUIEnvironment()->addImage(
		driver->getTexture("../../media/irrlichtlogoalpha2.tga"),
		core::position2d<s32>(10,10));

/*
create some nodes to delete
*/

// create a new delete callback
MyNodeDeleteNotifier* onDelete=new MyNodeDeleteNotifier();

for (i=0; i<5; i++) {
	deletenodes[i]=smgr->addCubeSceneNode();
	deletenodes[i]->setID(100*i);
	// assign the callback
	deletenodes[i]->setDeleteCallback(onDelete);
}


	/*
	We have done everything, so lets draw it. We also write the current
	frames per second and the name of the driver to the caption of the
	window.
	*/
	int lastFPS = -1;

	while(device->run())
	{
		driver->beginScene(true, true, video::SColor(255,113,113,133));

		smgr->drawAll(); // draw the 3d scene
		device->getGUIEnvironment()->drawAll(); // draw the gui environment (the logo)

		driver->endScene();

		int fps = driver->getFPS();

		if (lastFPS != fps)
		{
			core::stringw tmp(L"Movement Example - Irrlicht Engine [");
			tmp += driver->getName();
			tmp += L"] fps: ";
			tmp += fps;

			device->setWindowCaption(tmp.c_str());
			lastFPS = fps;
		}
	}

	/*
	In the end, delete the Irrlicht device.
	*/
	device->drop();

	return 0;
}
CuteAlien
Admin
Posts: 9926
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Post by CuteAlien »

First a minor thing:
When trying to get code accepted it is usually always (not just in Irrlicht) helpful to use the same style as used in that code. Irrlicht uses member variables starting with capital letters.

Actually i don't see right now why this would be needed. The engine should care about the cleanup of it's SceneNodes and if i derive my own SceneNodes which need addditional cleanup i have direct access to the destructor. Also it's not like the engine does randomly remove scenenodes at unpredicted times, but this happens only if you explicitly request it. Maybe there really are good reasons for this, i just don't see it yet.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
garrittg
Posts: 117
Joined: Wed Apr 20, 2005 6:17 pm
Location: Iowa, USA
Contact:

Post by garrittg »

greetings and thank you for the comments :)
CuteAlien wrote:Irrlicht uses member variables starting with capital letters.
gotcha. overlooked that thx.
CuteAlien wrote: Actually i don't see right now why this would be needed.
definately is not a must have but i do feel it adds value. this is based on my feeling that this provides an improved ability to better integrate with frameworks.
CuteAlien wrote: The engine should care about the cleanup of it's SceneNodes and if i derive my own SceneNodes which need addditional cleanup i have direct access to the destructor.
true. with this implementation the engine still doesnt care but it at least gives you the option to have cleanup with the stock scenenodes without having to create your own derivitives or wrap up the node removal in your own functions.
CuteAlien wrote: Also it's not like the engine does randomly remove scenenodes at unpredicted times, but this happens only if you explicitly request it.
nearly true. the possibility is there that remove can be called by anywhere in the engine now or in the future or by other folks code you are plugging in. CSceneNodeAnimatorDelete is a prime example of this. without knowing when the node is actually deleted or at least being guaranteed that it is your code calling the remove, it makes integrating that much more difficult.

i guess in the end i feel it adds better integration functionality at minimal cost.
CuteAlien
Admin
Posts: 9926
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Post by CuteAlien »

There's something i don't like at this callback. If you have an callback which returns the this pointer you sort of expect that you receive the whole object. But because this callback is called in the base destructor you will actually only get the members of ISceneNode. Shouldn't the callback be moved to IUnknown? In Drop() we would still have access to the whole object (and you would know it's real type at the moment where you add the callback and would have a chance to cast IUnknown correspondingly).

Btw. you can always prevent accidential deletion of SceneNodes by grabbing them yourself. Still your arguments are ok and it might even be useful sometimes.

Another idea - maybe a new event would be better for this? I remember that i needed something similar for IGuiElement and i did it by adding an event there. Thought it was slightly easier as gui-events do already exist.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Klasker
Posts: 230
Joined: Thu May 20, 2004 8:53 am
Contact:

Post by Klasker »

i still maintain that knowing when a scene node is actually deleted is extremely useful in cleanup of other game/app specific objects related to to the scene node
Consider rethinking your software design. The scene node should be removed when the game object it represents gets removed. NOT the other way around. In other words, the graphics depends on the game, but the game does not depend on the graphics.
I like to use the Model-View-Controller pattern when using Irrlicht, and have had nothing but good experiences with it. I like to use scene node animators to synchronize scene nodes with their game objects, so the game object itself never needs to know what scene nodes are attached to it. Separating game logic and graphical representation has (for me, at least) made game design a lot easier.
IPv6
Posts: 188
Joined: Tue Aug 08, 2006 11:58 am

Post by IPv6 »

On delete callback is useful sometimes, for example for "chained" animations, when node deletion trigger some other events, etc.

But there is no need in core changes. Just write animator that calls your callback in destructor and attach it to node. When node dies all animators are dies too. And here your callback can be called. Of course animator can make more interesting things too :)
garrittg
Posts: 117
Joined: Wed Apr 20, 2005 6:17 pm
Location: Iowa, USA
Contact:

Post by garrittg »

greetings :)
Consider rethinking your software design. The scene node should be removed when the game object it represents gets removed.
Just write animator that calls your callback in destructor and attach it to node.
definately correct... but there are still things that a programmer can do that will break it. clearing all the animators for example would falsely cause it to think the node was released. a customscenenode child that doesnt render could provide the same functionality and potentially be less risky as it may be more common to remove animators than children.

i guess im looking at it a bit differently in that from the standpoint of someone who provides a wrap of Irrlicht it is useful for me to be able to know some things that are going on within the engine. i cant guarantee the path the programmer is going to follow or know what animators/extras they will use. the best for me is to take into consideration everything provided by the Irrlicht API that may occur (ie animators calling remove, clearing all animators) and try to come up with a solution that is as foolproof as possible and that doesnt change how a programmer uses Irrlicht.
IPv6
Posts: 188
Joined: Tue Aug 08, 2006 11:58 am

Post by IPv6 »

I think you should avoid excessive decomposition. For real-time things even virtual call can be "too slow". i know the game where OOP was forcibly removed from code to gather CPU for physics and game simulations. Adding total foolproofness to the engine make it somewhat slow...
stodge
Posts: 216
Joined: Fri Dec 05, 2003 5:57 pm

Post by stodge »

"Consider rethinking your software design. The scene node should be removed when the game object it represents gets removed. NOT the other way around. In other words, the graphics depends on the game, but the game does not depend on the graphics. "

This makes more sense to me - I completely agree.
What does the debugger tell you? You did use the debugger, didn't you?
Post Reply