Irrlicht 3D Engine 笔记系列 之 自定义Animator

作者: i_dovelemon

日期: 2014 / 12 / 17

来源: CSDN

主题: Custom Animator, Referenced count

引言

在昨天的文章《Irrlicht 3D Engine 笔记系列 之 教程4 - Movement》中,博主向大家保证会在今天向大家实际操作如何扩展Irrlicht引擎的Animator特性。如果读者对Irrlicht的Animator的特性不是很了解的话,请先了解下前面的那篇文章,本片文章是在上次文章的基础上进行的。

Custom Animator

经过昨天我们的分析,发现只要我们继承ISceneNodeAnimator这个接口来实现我们自己的Animator就可以了。所以下面,我们就按着这个思路来对创建我们自定义的Animator。

首先,对我们将要创建的Animator进行下简单的描述:

博主想要创建一个沿着某条直线进行循环运动,并且物体本身在旋转的Animator。博主称之为Roll Animator(翻滚动画)。

好了,在明确了我们想要创建的Animator的功能之后,我们就实际动手来创建一个。

首先,我们来申明一个CMyRollAnimator的类,这个类继承至ISceneNodeAnimator,并且复写其中的两个纯虚拟函数animateNode和createClone,申明头文件如下所示:

//-------------------------------------------------------------------------------------------------
// declaration	: Copyright (c), by XJ , 2014. All right reserved .
// brief		: This file will define the custom roll animator.
// author		: XJ
// date			: 2014 / 12 / 17
// version		: 1.0
//--------------------------------------------------------------------------------------------------
#pragma once

#include<irrlicht.h>
using namespace irr ;
using namespace scene ;
using namespace core;

class CMyRollAnimator: public ISceneNodeAnimator
{
public:
	CMyRollAnimator(vector3df _startPos, vector3df _endPos,
		f32 _speed);

	//animate a node
	virtual void animateNode(ISceneNode* node, u32 timeMs);

	//clone a animator
	virtual ISceneNodeAnimator* createClone(ISceneNode* node, ISceneManager* newManager=0);

private:
	vector3df m_vStartPos ;
	vector3df m_vEndPos ;
	vector3df m_vCurPos ;
	f32       m_fSpeed  ;
	u32       m_uCurTime;

	//internal usage
	vector3df m_vDir    ;
	vector3df m_vRDir	;
};

读者可以看到博主上面定义的类非常的简单,只是添加了几个成员属性而已。除此之外就是复写的方法和一个构造函数。

为了实现前面描述的运动,我们来介绍下这里面成员的含义。首先,我们需要知道物体运动应该从哪里开始,到哪里结束,并且需要知道这个运动的速度以及当前运动的时间。另外两个成员属性用于内部计算使用。下面来看看这个类的明确实现是怎么样的:

#include"CMyRollAnimator.h"

//constructor
CMyRollAnimator::CMyRollAnimator(vector3df _startPos, vector3df _endPos,
		f32 _speed)
		:m_vStartPos(_startPos),
		m_vEndPos(_endPos),
		m_fSpeed(_speed),
		m_vCurPos(_startPos),
		m_uCurTime(0)
{
	m_vDir = m_vEndPos - m_vStartPos ;
	m_vDir.normalize();
	m_vRDir = m_vDir ;
	m_vRDir.rotateXZBy(90.0f);
}

//animate a node
void CMyRollAnimator::animateNode(ISceneNode* _pNode, u32 timeMs)
{
	if(0 == m_uCurTime)
	{
		m_uCurTime = timeMs ;
		_pNode->setPosition(m_vStartPos);
	}
	else
	{
		u32 _deltaTime = timeMs - m_uCurTime ;
		f32 _fDeltaTime = _deltaTime / 1000.0f ;
		m_uCurTime = timeMs ;
		m_vCurPos += _fDeltaTime * m_fSpeed * m_vDir ;

		if(abs(m_vCurPos.X - m_vEndPos.X) < 0.01f &&
			abs(m_vCurPos.Y - m_vEndPos.Y) < 0.01f &&
			abs(m_vCurPos.Z - m_vEndPos.Z) < 0.01f)
		{
			m_vCurPos = m_vStartPos ;
		}

		_pNode->setPosition(m_vCurPos);
		_pNode->setRotation(-m_vRDir * 180.0f * timeMs/1000.0f);
	}
}// end for animateNode

//clone an animator
ISceneNodeAnimator* CMyRollAnimator::createClone(ISceneNode* node, ISceneManager* newManager)
{
	CMyRollAnimator* _pAnimator = new CMyRollAnimator(m_vStartPos, m_vEndPos, m_fSpeed);
	return _pAnimator ;
}// end for createClone

其实实现,也十分的简单。最重要的控制方案是在animateNode这个函数里面实现的。对于这个函数,我们需要知道一点,它的参数列表中的第二个参数的意义并不是类似cocos2d-x里面的调用该函数的时间间隔。在Irrlicht内部维持了一个时间计数器,这个计数器从系统启动开始就开始计时,这里的timeMs就是系统此时的时间,以MS计算。所以读者大概,就能够明白博主为什么要在类内部定义的当前时间了。我们通过这个所谓的当前时间来与系统实际的当前时间进行比较,计算出时间间隔,从而控制节点进行移动。

想要实现自己的Animator是不是十分的简单啊???只要掌握了这个基本的规则,我们就可以发挥我们自己的想象来制作出十分复杂的Animator出来。大家可以借鉴下cocos2d-x里面的Action,试着在Irrlicht中也实现它们,并且将代码共享出来给大家使用。如果有空的话,博主可能会自己实现一些Animator共享给大家使用。

完整的代码和教程4中的代码一致。所不同的是将FlyCircleAnimator换成我们这里自己定义的Animator而已,如下所示:

#include<irrlicht.h>
#include"MyEventReceiver.h"
#include"CMyRollAnimator.h"
using namespace irr;
using namespace core;
using namespace gui;
using namespace scene;
using namespace video;

#ifdef _IRR_WINDOWS_
#pragma comment(lib,"irrlicht.lib")
#pragma comment(linker,"/subsystem:windows /ENTRY:mainCRTStartup")
#endif

int main()
{
	//Create the irrdevice
	MyEventReceiver _receiver;
	IrrlichtDevice* _pDevice = createDevice(EDT_DIRECT3D9,dimension2d<u32>(800,640),32U,
		false,
		false,
		false,
		&_receiver);

	//Check if create successfully
	if(NULL == _pDevice)
		return 1 ;

	//Get the video driver and scene manager
	IVideoDriver* _pVideoDriver = _pDevice->getVideoDriver();
	ISceneManager* _pSceneManager = _pDevice->getSceneManager();

	//Create a sphere node
	ISceneNode* _pNode = _pSceneManager->addSphereSceneNode();

	//Check if create successfully
	if(NULL == _pNode)
		return 1 ;
	_pNode->setPosition(vector3df(0,0,30));
	_pNode->setMaterialTexture(0,_pVideoDriver->getTexture("wall.bmp"));
	_pNode->setMaterialFlag(EMF_LIGHTING,false);

	//Create a cube node
	ISceneNode* _pCubeNode = _pSceneManager->addCubeSceneNode();

	//Check if create successfully
	if(NULL == _pCubeNode)
		return 1 ;
	_pCubeNode->setMaterialTexture(0,_pVideoDriver->getTexture("t351sml.jpg"));
	_pCubeNode->setMaterialFlag(EMF_LIGHTING, false);

	//Create a scene node animator for cube node
	//ISceneNodeAnimator* _pAnimator = _pSceneManager->createFlyCircleAnimator(vector3df(0,0,30),
	//	20.0f);
	CMyRollAnimator * _pAnimator = new CMyRollAnimator(vector3df(-20,0,30), vector3df(20,0,30),
		10.0f);

	//Check if create successfully
	if(NULL == _pAnimator)
		return 1 ;

	_pCubeNode->addAnimator(_pAnimator);

	//Drop the animator
	_pAnimator->drop();

	//Add one camera node
	_pSceneManager->addCameraSceneNode();

	int _nlastFPS = -1 ;

	u32 _uLastTime = _pDevice->getTimer()->getTime();

	const f32 MOVEMENT_SPEED = 5.0f ;

	//Do loop
	while(_pDevice->run())
	{
		const u32 _now = _pDevice->getTimer()->getTime();
		const f32 _frameDeltaTime = (f32)(_now - _uLastTime)/1000.0f;
		_uLastTime = _now ;

		vector3df _pos = _pNode->getPosition();

		if(_receiver.IsKeyDown(KEY_KEY_W))
			_pos.Y += MOVEMENT_SPEED * _frameDeltaTime ;
		else if(_receiver.IsKeyDown(KEY_KEY_S))
			_pos.Y -= MOVEMENT_SPEED * _frameDeltaTime ;

		if(_receiver.IsKeyDown(KEY_KEY_A))
			_pos.X -= MOVEMENT_SPEED * _frameDeltaTime ;
		else if(_receiver.IsKeyDown(KEY_KEY_D))
			_pos.X += MOVEMENT_SPEED * _frameDeltaTime ;

		_pNode->setPosition(_pos);

		//Draw the scene
		_pVideoDriver->beginScene();
		_pSceneManager->drawAll();
		_pVideoDriver->endScene();
	}// end while

	_pDevice->drop();

	return 0 ;
}// end 

Referenced Counter

博主在编写这个实验程序的时候,发现当我们调用_pAnimator->drop的时候,animator并没有销毁。所以,博主对于引擎内部是如何保存一个对象技术感到好奇,决心研究一下。

在Irrlicht引擎中,通过一个十分简单的引用技术(Referenced Counter)系统来对系统中的对象进行跟踪。这个功能是通过一个名为IReferenceCounted接口来实现的,引擎中大部分的类都继承了这个接口,从而实现了引用技术操作。下面来看下,这个类的申明:

// Copyright (C) 2002-2012 Nikolaus Gebhardt
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h

#ifndef __I_IREFERENCE_COUNTED_H_INCLUDED__
#define __I_IREFERENCE_COUNTED_H_INCLUDED__

#include "irrTypes.h"

namespace irr
{

	//! Base class of most objects of the Irrlicht Engine.
	/** This class provides reference counting through the methods grab() and drop().
	It also is able to store a debug string for every instance of an object.
	Most objects of the Irrlicht
	Engine are derived from IReferenceCounted, and so they are reference counted.

	When you create an object in the Irrlicht engine, calling a method
	which starts with 'create', an object is created, and you get a pointer
	to the new object. If you no longer need the object, you have
	to call drop(). This will destroy the object, if grab() was not called
	in another part of you program, because this part still needs the object.
	Note, that you only need to call drop() to the object, if you created it,
	and the method had a 'create' in it.

	A simple example:

	If you want to create a texture, you may want to call an imaginable method
	IDriver::createTexture. You call
	ITexture* texture = driver->createTexture(dimension2d<u32>(128, 128));
	If you no longer need the texture, call texture->drop().

	If you want to load a texture, you may want to call imaginable method
	IDriver::loadTexture. You do this like
	ITexture* texture = driver->loadTexture("example.jpg");
	You will not have to drop the pointer to the loaded texture, because
	the name of the method does not start with 'create'. The texture
	is stored somewhere by the driver.
	*/
	class IReferenceCounted
	{
	public:

		//! Constructor.
		IReferenceCounted()
			: DebugName(0), ReferenceCounter(1)
		{
		}

		//! Destructor.
		virtual ~IReferenceCounted()
		{
		}

		//! Grabs the object. Increments the reference counter by one.
		/** Someone who calls grab() to an object, should later also
		call drop() to it. If an object never gets as much drop() as
		grab() calls, it will never be destroyed. The
		IReferenceCounted class provides a basic reference counting
		mechanism with its methods grab() and drop(). Most objects of
		the Irrlicht Engine are derived from IReferenceCounted, and so
		they are reference counted.

		When you create an object in the Irrlicht engine, calling a
		method which starts with 'create', an object is created, and
		you get a pointer to the new object. If you no longer need the
		object, you have to call drop(). This will destroy the object,
		if grab() was not called in another part of you program,
		because this part still needs the object. Note, that you only
		need to call drop() to the object, if you created it, and the
		method had a 'create' in it.

		A simple example:

		If you want to create a texture, you may want to call an
		imaginable method IDriver::createTexture. You call
		ITexture* texture = driver->createTexture(dimension2d<u32>(128, 128));
		If you no longer need the texture, call texture->drop().
		If you want to load a texture, you may want to call imaginable
		method IDriver::loadTexture. You do this like
		ITexture* texture = driver->loadTexture("example.jpg");
		You will not have to drop the pointer to the loaded texture,
		because the name of the method does not start with 'create'.
		The texture is stored somewhere by the driver. */
		void grab() const { ++ReferenceCounter; }

		//! Drops the object. Decrements the reference counter by one.
		/** The IReferenceCounted class provides a basic reference
		counting mechanism with its methods grab() and drop(). Most
		objects of the Irrlicht Engine are derived from
		IReferenceCounted, and so they are reference counted.

		When you create an object in the Irrlicht engine, calling a
		method which starts with 'create', an object is created, and
		you get a pointer to the new object. If you no longer need the
		object, you have to call drop(). This will destroy the object,
		if grab() was not called in another part of you program,
		because this part still needs the object. Note, that you only
		need to call drop() to the object, if you created it, and the
		method had a 'create' in it.

		A simple example:

		If you want to create a texture, you may want to call an
		imaginable method IDriver::createTexture. You call
		ITexture* texture = driver->createTexture(dimension2d<u32>(128, 128));
		If you no longer need the texture, call texture->drop().
		If you want to load a texture, you may want to call imaginable
		method IDriver::loadTexture. You do this like
		ITexture* texture = driver->loadTexture("example.jpg");
		You will not have to drop the pointer to the loaded texture,
		because the name of the method does not start with 'create'.
		The texture is stored somewhere by the driver.
		\return True, if the object was deleted. */
		bool drop() const
		{
			// someone is doing bad reference counting.
			_IRR_DEBUG_BREAK_IF(ReferenceCounter <= 0)

			--ReferenceCounter;
			if (!ReferenceCounter)
			{
				delete this;
				return true;
			}

			return false;
		}

		//! Get the reference count.
		/** \return Current value of the reference counter. */
		s32 getReferenceCount() const
		{
			return ReferenceCounter;
		}

		//! Returns the debug name of the object.
		/** The Debugname may only be set and changed by the object
		itself. This method should only be used in Debug mode.
		\return Returns a string, previously set by setDebugName(); */
		const c8* getDebugName() const
		{
			return DebugName;
		}

	protected:

		//! Sets the debug name of the object.
		/** The Debugname may only be set and changed by the object
		itself. This method should only be used in Debug mode.
		\param newName: New debug name to set. */
		void setDebugName(const c8* newName)
		{
			DebugName = newName;
		}

	private:

		//! The debug name.
		const c8* DebugName;

		//! The reference counter. Mutable to do reference counting on const objects.
		mutable s32 ReferenceCounter;
	};

} // end namespace irr

#endif

这个接口十分的简洁,我们只要通过grab()和drop()这两个函数来进行操作就可以。操作的规则在上面已经说的非常清楚了。对于在引擎中使用create方法创造出来的对象,一般来说在create方面里面会调用new来构造一个对象。看这个接口的构造函数,我们发现,进行构造之后引用计数就是1了。如果我们接着调用drop()函数,引用计数值就会变成0,从而自我进行销毁。也就是说,调用drop的次数要比调用grab的次数大1才能够使引用计数值变为0,从而销毁自身。

这就是导致在上面,博主调试实验程序的时候,发现调用_pAnimator->drop()的时候,并没有自我销毁的原因。在我们将animator添加到node里面的时候,node中会保存一个这个对象的副本,从而调用一次grab()函数。也就是,我们需要再次的调用一次drop函数,才能够销毁掉这个animator。但是,博主这里并没有这么做。亲爱的读者们啊,也请你千万不要这么做。我们上面的所有创造过程,都是在程序主循环之上进行构造的。也就是说,在程序的主循环中还需要使用我们创建的这个animator,从而才能够随着时间的进行,控制节点进行移动。

那么,就有一个问题了,这个对象到底在什么地方进行最终的销毁了?

博主发现,在教程4的结尾处,当程序主循环结束之后,会调用一下_pDevice->drop()函数。是不是和这个对象的释放有关了?

我们跟踪这个类的析构方法,依次得到如下的代码:

CIrrDeviceStub::~CIrrDeviceStub()
{
	VideoModeList->drop();
	FileSystem->drop();

	if (GUIEnvironment)
		GUIEnvironment->drop();

	if (VideoDriver)
		VideoDriver->drop();

	if (SceneManager)
		SceneManager->drop();

	if (InputReceivingSceneManager)
		InputReceivingSceneManager->drop();

	if (CursorControl)
		CursorControl->drop();

	if (Operator)
		Operator->drop();

	if (Randomizer)
		Randomizer->drop();

	CursorControl = 0;

	if (Timer)
		Timer->drop();

	if (Logger->drop())
		os::Printer::Logger = 0;
}

CIrrDeviceStub是Irrlicht引擎中所有IrrlichtDevice设备的共有的一个桩类。用于实现那些所有设备都相同的部分。通过这个桩类在IrrlichtDevice这个接口与实际的CIrrDeviceWin32之间进行一个桥接,实现那些共有的功能。在这个桩类析构函数中,发现会依次的调用各个子系统的drop函数,从而释放里面的资源。对于node来说,它保存在SceneManager里面,继续跟踪,发现在引擎中只有一个SceneManager,为CSceneManager。查看这个类的析构函数,得到如下的函数:

//! destructor
CSceneManager::~CSceneManager()
{
	clearDeletionList();

	//! force to remove hardwareTextures from the driver
	//! because Scenes may hold internally data bounded to sceneNodes
	//! which may be destroyed twice
	if (Driver)
		Driver->removeAllHardwareBuffers();

	if (FileSystem)
		FileSystem->drop();

	if (CursorControl)
		CursorControl->drop();

	if (CollisionManager)
		CollisionManager->drop();

	if (GeometryCreator)
		GeometryCreator->drop();

	if (GUIEnvironment)
		GUIEnvironment->drop();

	u32 i;
	for (i=0; i<MeshLoaderList.size(); ++i)
		MeshLoaderList[i]->drop();

	for (i=0; i<SceneLoaderList.size(); ++i)
		SceneLoaderList[i]->drop();

	if (ActiveCamera)
		ActiveCamera->drop();
	ActiveCamera = 0;

	if (MeshCache)
		MeshCache->drop();

	for (i=0; i<SceneNodeFactoryList.size(); ++i)
		SceneNodeFactoryList[i]->drop();

	for (i=0; i<SceneNodeAnimatorFactoryList.size(); ++i)
		SceneNodeAnimatorFactoryList[i]->drop();

	if (LightManager)
		LightManager->drop();

	// remove all nodes and animators before dropping the driver
	// as render targets may be destroyed twice

	removeAll();
	removeAnimators();

	if (Driver)
		Driver->drop();
}

在这个析构函数中,可以发现有一个名为removeAnimators()的函数,我们继续跟踪,如下所示:

		//! Removes all animators from this scene node.
		/** The animators might also be deleted if no other grab exists
		for them. */
		virtual void removeAnimators()
		{
			ISceneNodeAnimatorList::Iterator it = Animators.begin();
			for (; it != Animators.end(); ++it)
				(*it)->drop();

			Animators.clear();
		}

哈哈,在这里,调用了一次drop(),从而实现了对Animator对象的销毁。

在这次的追踪发现了,当我们调用_pDevice->drop函数的时候,会依次的释放系统中所有的资源和对象。

但是这里,博主又有一个疑问?很多对象只有在这最后的步骤才会调用实际的析构函数进行销毁。那么也就是说,如果在系统的中途,我们已经决定了不再使用某个对象,并且保证在之后的过程中都不再使用了,如果只是简单的调用一次drop是不是还是会让这个对象依然残留在系统里面,从而导致内存被占用,不需要的资源长期持有着了?Irrlicht引擎是否有某种机制能够让我们主动的释放掉一些资源了?查看源代码发现,的确有这样的一些方法。大部分都是通过remove开头的来实现这样的功能。但是请注意,千万不要主动的调用两次drop()函数。虽然这样的确能够将对象提前销毁,但是违背了使用引言计数的初衷,在Irrlicht内部会出现野指针的情况。毕竟你还是需要和Irrlicht打交道的,这样做实在不好也不合理。最适当的方法,就是通过remove方法来通知Irrlicht来释放掉你不想使用的资源。

现在给大家总结下,本篇文章的核心内容:

1.通过继承ISceneNodeAnimator来实现我们自己的Animator

2.Irrlicht引用计数系统十分的简单,却非常的实用,请大家按照它定义的规则来使用它,否则会出现意想不到的结果。

在博主自己进行分析Irrlicht所使用的各种框架技术之后,博主有股冲动,想要自己的来实现下或者说仿制下这些技术。毕竟,研究引擎,一方面是为了熟悉它,使用它,另外一个更重要的原因是为了学习这些技术,并把它据为己有。所以,在今后,博主可能会另外在开一个系列,专门用来仿制引擎中的各种特性,试着实现一些仿真程序。

最后给出,本次试验程序的程序截图,十分的简单,一个Cube来回的移动而已:

这次的文章就到此结束了,感谢大家的关注!!!

时间: 2024-10-10 03:00:59

Irrlicht 3D Engine 笔记系列 之 自定义Animator的相关文章

Irrlicht 3D Engine 笔记系列 之 自己定义Animator

作者: i_dovelemon 日期: 2014 / 12 / 17 来源: CSDN 主题: Custom Animator, Referenced count 引言 在昨天的文章<Irrlicht 3D Engine 笔记系列 之 教程4 - Movement>中,博主向大家保证会在今天向大家实际操作怎样扩展Irrlicht引擎的Animator特性.假设读者对Irrlicht的Animator的特性不是非常了解的话,请先了解下前面的那篇文章,本片文章是在上次文章的基础上进行的. Cust

Irrlicht 3D Engine 笔记系列之 教程4 - Movement

作者: i_dovelemon 日期: 2014 / 12 / 16 来源: CSDN 主题: Event Receiver, Animator, Framerate independent movement and framerate dependent movement 引言 从今天開始,博主将进行对3D Engine的学习.而且,在博客中将自己学习的心得一一分享给大家.希望可以对大家有所帮助.也希望可以找到志同道合的同伴一起学习3D 游戏引擎方面的知识. 为什么选择Irrlicht? 在非

Irrlicht 3D Engine 笔记系列 之 教程6- 2D Graphics

作者:i_dovelemon 日期:2015 / 7 / 1 来源: CSDN 主题:2D Graphics, Irrlicht 教程翻译 本篇教程将要向大家展示如何使用Irrlicht引擎绘制2D图形.绘制2D图形能够让我们制作一个2D游戏或者绘制一些漂亮的用户界面和HUD出来. 和以前一样,包含一些头文件,使用irr命名空间,并且通知连接器链接lib文件: #include <irrlicht.h> #include "driverChoice.h" using nam

Irrlicht 3D Engine 笔记系列 之 教程5- User Interface

作者:i_dovelemon 日期:2014 / 12 / 18 来源:CSDN 主题:GUI 引言 今天,博主学习了第五个教程.这个教程讲解了如何使用Irrlicht内置的一个基础模块,GUI模块,来开发一些GUI程序.作为Irrlicht的重要基础模块,博主有必要对此进行一些代码跟踪和深入的了解.详细的教程过程请看官网. 设备选择 以前在使用Ogre的时候,它的程序运行之初都会问你需要使用的是哪一种API.那么在Irrlicht引擎中是否有同样的功能了?答案是肯定,在Irrlicht的dri

Oracle Apex 实用笔记系列 4 - 自定义javascript

对于自定义的javascript,有如下几种方式 1. 页面加载 在组件视图 1.点击 编辑page 2.在javascript标签页的Function and global variable declaration处,书写自定义的javascript函数; 3.在Execute when page loads处调用或执行javascript代码 2.Dynamic Action 1.创建一个dynamic action; 2.选择事件类型,比如change, click, page load等

Unreal Engine 4 系列教程 Part 1:入门

.katex { display: block; text-align: center; white-space: nowrap; } .katex-display > .katex > .katex-html { display: block; } .katex-display > .katex > .katex-html > .tag { position: absolute; right: 0px; } .katex { font: 1.21em/1.2 KaTeX_M

Hadoop学习笔记系列文章导游【持续更新中...】

一.为何要学习Hadoop? 这是一个信息爆炸的时代.经过数十年的积累,很多企业都聚集了大量的数据.这些数据也是企业的核心财富之一,怎样从累积的数据里寻找价值,变废为宝炼数成金成为当务之急.但数据增长的速度往往比cpu和内存性能增长的速度还要快得多.要处理海量数据,如果求助于昂贵的专用主机甚至超级计算机,成本无疑很高,有时即使是保存数据,也需要面对高成本的问题,因为具有海量数据容量的存储设备,价格往往也是天文数字.成本和IT能力成为了海量数据分析的主要瓶颈. Hadoop这个开源产品的出现,打破

PHP开发笔记系列(一)-PDO使用

之前一段时间,开始了php的研究,看了关于PDO的一些资料,发现不错,整理和总结一下,作为开发笔记,留待日后使用,<PHP开发笔记系列(一)-PDO使用>. PDO是PHP Data Objects的简称,是一种数据库访问抽象层.PDO是用于多种数据库的一致接口.类比的说,PDO做的事情类似于JAVA中的持久层框架(Hibernate.OpenJPA)的功能,为异构数据库提供一个统一的编程接口,这样就不必再使用mysql_*.pg_*这样的函数,也不必再写自己的"GenericDAO

iOS流布局UICollectionView系列四——自定义FlowLayout进行瀑布流布局

iOS流布局UICollectionView系列四--自定义FlowLayout进行瀑布流布局 一.引言 前几篇博客从UICollectionView的基础应用到设置UICollectionViewFlowLayout更加灵活的进行布局,但都限制在系统为我们准备好的布局框架中,还是有一些局限性,例如,如果我要进行瀑布流似的不定高布局,前面的方法就很难满足我们的需求了,如下: 这种布局无疑在app的应用中更加广泛,商品的展示,书架书目的展示,都会倾向于采用这样的布局方式,当然,通过自定义FlowL