2009年1月31日星期六

Streams, Readers and Writers

Nebula3 IO子系统提供Stream对象作为通用的数据存储和传输通道。stream的子类实现或者包装特定的数据传输协议。Stream类一般和URI相关联。

* "file:" scheme maps to IO::FileStream class
* "http:" scheme maps to Net::HttpStream class
* "mem:" scheme maps to IO::MemoryStream class

Nebula3现在提供了这些开箱即用的URI schemes和相关stream类。Stream对象一般是通过
IO::Server::CreateStream()方法来创建的,该方法带有一个URI参数并返回一个适当的Stream对象:

// create a HttpStream and a FileStream object
IO::Server* ioServer = IO::Server::Instance();
Ptr<> httpStream = ioServer->CreateStream("http://www.radonlabs.de/index.html);
Ptr<> fileStream = ioServer->CreateStream("home:readme.txt");

一个应用程序可以使用IO::Server::RegisterUriScheme()方法把stream类和URI schemes关联起来。

Stream仅仅提供通用的Read()和Write()方法去读写内存块。可以通过很简便的方式读写流数据,StreamReader和StreamWriter就是用来读写流的。不同的StreamReader和StreamWriter子类提供读写不同类型的数据。Nebula3 IO子系统提供了以下的读写类:

* BinaryReader/Writer: 读写二进制格式的流
* TextReader/Writer: 读写文本流
* XmlReader/Writer: 读写XML格式的数据

下面的代码展示了从HTTP服务上解析一个简单的XML文件:

// create a stream and attach an XML reader to it
Ptr< Stream> stream = IO::Server::Instance()->CreateStream("http://www.radonlabs.de/books.xml");
Ptr< XmlReader> xmlReader = XmlReader::Create();
xmlReader-> SetStream(stream);

// start reading...
if (xmlReader-> Open())
{
// iterate through "books" xml elements
if (xmlReader-> SetToFirstChild("books")) do
{
// read attributes from current element
String author = xmlReader-> GetString("author");
int year = xmlReader-> GetInt("year");
int numPages = xmlReader->GetInt("pages");
...
}
while (xmlReader-> SetToNextChild("books"));
xmlReader->Close;
}

其它Nebula3子系统可以继承StreamReader和StreamWriter实现自己的读写类。例如,消息子系统为了持久化message对象就提供了MessageReader和MessageWirter类。

原文: Streams, Readers and Writers

[声明]:限于译者水平,文中难免错漏之处,欢迎各位网友批评指正;

Nebula3 IO子系统:URIs和Assigns

Nebula3使用URIs(统一资源标示)代替简单的文件名称来定位IO资源。URI对象的创建方式如下:

using namespace IO;
URI webUri("http://www.myhttpserver.com/index.html");
URI localUri("file:///c:/temp/text.txt");

第一个URI指向一个在HTTP服务器上的文件,第二个URI指向一个在本地文件系统的文件。当一个URI对象被创建后,将被拆分成几个部分:

* the scheme (for instance "http", or "file")
* the host ("www.myhttpserver.com")
* the local path ("index.html", or "c:/temp/text.txt")

URIs一般还包含一些额外的信息:

* user info: describes login information for a secure service
* port number: describes a network port number
* fragment: usually describes a location inside an HTML file
* query: a set of key/value pairs often used by web services

Nebula3已经内置支持了“file:”和“http:”,在不久的将来也计划支持“ftp:”。

例子:从http服务器上拷贝一个文件到本地:

URI from("http://www.radonlabs.de/index.html");
URI to("file:///c:/temp/index.html");
IO::Server::Instance()->CopyFile(from, to);

Assigns在Nebula1中就已经存在了,它是文件系统位置的一个别名(或快捷方式),在一个Nebula应用程序中可以很方便抽象文件的位置。

例如,你可以很简单使用“textures:mycategory/mytexture.dds”来代替“C:/ProgramFiles/MyApp/export/textures/category/mytexture.dds”。这个“textures:”将被定义为“C:/Programme/MyApp/export/textures”。

Assigns在说明应用程序安装目录或者当前登陆用户的目录的位置时特别有用。由于这个原因,Nebula3在程序启动的时候定义了几个标准的Assigns:

home: 指向应用程序安装目录。
bin: 指向应用程序可执行文件目录。
temp: 指向一个当前用户可读可写的临时文件目录。
user: 指向登陆用户目录。在英文操作系统中,该目录的位置是“My Files/CompanyName /AppName”。该目录必需保证是可写的,因为该目录将存放配置信息或者是游戏存档。

在Nebula3中,Assigns扩展了URIs的功能。例如,“textures:”可以被定义为
“http://www.radonlabs.de/myapp/textures”,那么在加载纹理的时候就会自动地从http服务器上获取而不是本地文件系统。Assign能被内嵌,因此“textures:”可以被定义为“home:export/texutres”,在使用的时候就自动转化为安装目录下的“export/textures”目录。

原文:The Nebula3 IO subsystem: URIs and Assigns

[声明]:限于译者水平,文中难免错漏之处,欢迎各位网友批评指正;

Nebula3多线程架构

在Nebula3中,代码在两种不同场景中并行运行。第一种场景我们称为“Fat Threads”。一个Fat Thread在特定的cpu内核上运行一个完整子系统(像渲染,音频,人工智能,物理,资源管理)。

另一种类型的线程我们称为“Job”。一个Job就是一块数据和处理该数据的c++代码。Job对象由job调度器来处理,把job分配到低负荷的cpu内核上以尽量保证cpu内核处于忙碌状态。

设计一个完整的系统并以某种方式保证在任何时候所有核心处于忙碌状态,就目前来讲当然是一种挑战。

这就是我期待有更多的试验和更好的调优。

第二个挑战是让程序员的生活过得尽可能地简单。一个游戏应用程序员在任何时候都不需要关心他运行在一个多线程环境中。他不必担心创建一个死锁或者覆写其它线程数据。他也不需要关注临界点,事件和信号量。并且整个引擎架构是健壮的。某种意义上大部分传统多线程代码是脆弱的,那是因为会发生资源竞争或者忘记了临界条件会破坏数据。

当需要共享数据或者两个线程间需要发生通信时多线程就变得难于预料了。

从大的范围看,Nebula3是通过叫做“并行Nebulas”的概念来解决这两个问题的。方法是每个Fat Thread都拥有自己的最小运行库来运行一个完整的子系统,其中包含该子系统所需的所有组件。因此如果一个运行在它自己线程中的子系统需要操作文件,那么它就单独拥有一个文件服务子系统。这种解决方案的优点是,在nebula中的大部分代码不会意识到自己运行在一个多线程环境中,并且所有Fat Thread之间不需要共享数据。每个nebula核心完全独立于其它nebula核心运行着。缺点当然是有的了,因冗余的数据而浪费一些内存,但紧紧是几K,而不是几M。

这些冗余的数据消除了精细的线程锁,让程序员不必要为了多线程的安全去考虑每一行代码。

但当然了,Fat Thread之间会发生通讯,不然整个设计概念就没用了。这里的想法是建立一种并且仅仅只有一种标准的系统通讯方式,并且这种通讯是可靠和快速的。这就是消息

系统的由来。只能通过发送消息来跟Fat Thread通讯。消息是带着一些数据简单的c++对象,并有一些set/get方法。通过这种方式通讯,只有消息子系统的代码才需要线程安全(访问消息携带的资源,如内存缓冲区,必须受到限制因为它们是共享数据)。

这个方法解决了Fat Thread场景中大多数多线程问题,但是对于解决job对象问题却没有任何帮助。Nebula3在某些地方需要严格限制job对象可以做什么或者不能做什么。最直接的限制是job在不能内存缓冲区内做计算。以这种方式,jobs就不需要复杂的运行库(不使用文件IO,不需要访问渲染模块等等)。如果这还不够的话,一个“job运行环境”必须被定义,就像Fat Thread那样。job不能开启一个线程,它只能被线程池中的线程调度。这在运行开销区间内不会有太大的问题。

到目前为止,只有Nebula3 IO子系统作为概念证明实现了Fat Thread,并且运行得很不错。如果要做传统的同步IO操作,Nebula3只要简单地直接调用本地线程的IO子系统就可以了。因此,例如要列出文件夹里的内容或者是删除文件,只要简单地调用一个c++方法就可以做到。对于异步IO操作,一些通用的IO操作已经被很好定义为消息了(i.e. ReadStream, WriteStream, CopyFile, DeleteFile, etc...)。做异步IO操作也仅仅需要几行代码就可以了:创建一个消息对象,填入数据,把消息发送到一个单例的IOInterface上。如果需要,可以等待或者是轮询这个异步IO操作。

这样的好处是,整个IO子系统不需要多线程相关的代码,因为在各个Fat Thread中的IO子系统是完全独立的(当然了,在某些点上还是会发生同步的IO操作,但把这些统统留给操作系统来完成)。

原文: Nebula3 Multithreading Architecture

[声明]:限于译者水平,文中难免错漏之处,欢迎各位网友批评指正;

2009年1月30日星期五

消除虚函数的调用

在Nebula2中,平台抽象是通过子类和虚函数来实现的。例如,nGfxServer2类实现了一个平台无关的图形接口,而子类nD3D9Server通过覆写父类的虚方法实现了Direct3D9版本。客户代码和nGFxServer2接口交互并不需要关心是通过Direct3D实现渲染的或者是其它的渲染API。

根据不同的平台,虚函数的调用性能从一般糟到很糟,因为额外的内存查找可能导致缓存无效,flush the instruction pipeline, disable branch prediction,etc...
虚函数调用一直是多态最快的方式,但这对于平台抽象一般不是必须的,编译时多态就足够了。

Nebula3通过typedef-ing来实现平台抽象,这样消除了很多虚方法的调用并且在不牺牲平台独立的情况下把一些常用的方法定义为内联方法。

首先编写一个基类定义类的接口。这个基类通常没有虚函数(除了析构函数,这个类一般是继承Core::RefCounted)。一个平台相关的类继承了基类,用平台相关的代码覆写了基类的大部分或者全部的方法。最后,把一个平台相关的类用一个合适的平台无关的类名来定义。

这边有个例子:让我们看看是如何实现CoreGraphics名字空间中的RenderDevice类的.首先在基类中定义类的接口:

namespace CoreGraphics
{
class RenderDeviceBase : public Core::RefCounted
{
DeclareClass(RenderDeviceBase);
DeclareSingleton(RenderDeviceBase);
public:
/// constructor
...
};

} // namespace CoreGraphics

注意这个类名是RenderDeviceBase,不是RenderDevice。

一个叫做D3D9RenderDevcie平台相关的子类继承了RenderDeviceBase这是RenderDevice类的
Direct3D9的一个实现。


namespace CoreGraphics
{
class D3D9RenderDevice : public RenderDeviceBase
{
...
};

} // namespace CoreGraphics

最后,为RenderDevice类选择一个合适的头文件,根据条件把一个平台相关的类用一个合适的平台无关的类名来定义。

#if __USE_DIRECT3D9__
#include "coregraphics/d3d9/d3d9renderdevice.h"
namespace CoreGraphics
{
typedef D3D9RenderDevice RenderDevice;
}
#elif __USE_DIRECT3D10__
#include "coregraphics/d3d10/d3d10renderdevice.h"
namespace CoreGraphics
{
typedef D3D10RenderDevice RenderDevice;
}
#elif __USE_OPENGL__
#include "coregraphics/ogl/oglrenderdevice.h"
namespace CoreGraphics
{
typedef OGLRenderDevice RenderDevice;
}
#else
#error "RenderDevice class not implemented on this platform!"
#endif

客户代码和RenderDevice打交道,完全不知道实际是使用D3D9RenderDevice或D3D10RenderDevice类.平台独立将在编译的时候就解决掉,所有调用RenderDevice的方法都是一般调用,没有虚函数调用。这个可以让编译器做更多的优化工作。

原文: Getting rid of virtual method calls

[声明]:限于译者水平,文中难免错漏之处,欢迎各位网友批评指正;

2009年1月29日星期四

Nebula3引用计数和智能指针

c++只为在栈上创建的对象才提供自动的生命周期管理。当离开了c++的上下文,栈上的对象将被自动清除掉。

{
//create a new object on the stack
MyObject obj;

//do something with obj...

//current context is left,obj is destroyed automatically
}

当在堆上创建一个对象时,该对象就必须手动删除,否则将出现内存泄露:

{
//create an object on the heap
MyObject *objPtr = new MyObject();

//do something with obj...

//need to manually destroy obj
delete obj;
}

这将使事情变得复杂,当一个c++对象被其它多个对象使用,那么这时就必须要定义所有权规则(拥有该c++对象的类要负责删除该对象,而其它类只是使用该对象)。

在一个复杂的系统中,这种所有权管理将很快变得很棘手。引用计数可以较好地解决这种问题。通过引用计数,不需要定义所有权关系,该对象被使用时就把引用计数增加,当该对象不需要再被使用时就调用Release()方法减少引用计数。当引用计数降为0是,该对象就被删除。这个方法解决了多个对象同时引用一个对象的问题,但还是需要程序员在合适的的时候手动调用Release()方法。

智能指针可以很好地解决第二个问题。智能指针就是一个指向其它c++对象的c++模板对象。管理引用计数的创建,销毁和赋值。很多时候智能指针的使用和普通指针一样,除此之外它修复了普通指针所带来的危险。

让我们看看在Nebula3中是如何实现以上设想的:

{
//create a c++ object on the heap
Ptr<myobject > obj = MyObject.Create();

//do something with obj
obj->DoSomething();

//at the end of context,the smart pointer object is destroyed
//and will release its target object
}

有了智能指针,一个堆对象看起来就像一个栈对象,不需要额外关注对象的释放。智能指针同样很好地解决了指针数组的问题,如果你想创建一个带普通指针的动态数组,你必须在删除数组之前手动地删除数组中的每一个对象,因为当数组被删除时数组中的普通指针指向的对象是不会自动删除的。通过智能指针创建数组,这个问题就能很好地被解决掉。当数组被释放掉时,它将调用容器中的智能指针的析构函数,这样就能释放掉智能指针所指的对象。

{
//create an array of smart pointers
Array <ptr<myobject>> objArray;

//create objects and append to array
int i;
for(i=0;i<10;i++){ href="http://flohofwoe.blogspot.com/2007/01/nebula3-ref-counting-and-smart-pointers.html">Nebula3 ref-counting and smart pointers

[声明]:限于译者水平,文中难免错漏之处,欢迎各位网友批评指正;

Nebula3基础层概述

Nebula3基础层为渲染和音频提供一个通用的平台抽象服务。它位于Nebula3层次模型中的最底层并且可以为其它不需要3d渲染应用程序提供一个易于使用的底层框架。

基础层已基本完成了,并带有完整的测试类和性能测试类。

基础层包含以下几个子模块:

1.Core:实现基本Nebula3对象模型,并且支持引用计数智能指针动态运行时(RTTI)和通过类名或者FourCC创建对象。

2.Memory:提供内存管理和一些包装的方法。

3.Util:一些工具类,不同类型的容器类,一个功能强大的字符串类,一个guid包装类等等......

4.Timing:提供时间管理的类。

5.IO:一个全新的功能强大的IO子系统,它的设计灵感来源于.NET IO框架。使用URI作为IO资源标识符,stream类提供通用的数据通道,

6.Threading:为多线程提供一个底层的类包装。

7.Messaging:Mangalore消息系统的一个升级版本。为相同线程不同线程或者在同一台机器上的两个应用程序或者网络上的对象间提供一个标准通信方式。

8.Math:一个单独的向量数学库。它的设计思想是使数学库的代码看起来像HLSL shader代码,并且在多种平台和编译器上提供最好的执行效率。

9.Net:一个底层的网络子系统。为socket,IP address,一个简单客户/服务器系统提供包装,并提供基本的HTTP支持。

10.Scripting:Nebula3的脚本系统比Nebula2来的易用,并且容易扩展。现在标准脚本语言是LUA(在Nebula2中是TCL),大大降低了内存空间。脚本现在不再是绑定在架构上。很容易禁用脚本提高内存的利用率。当然也可以增加其它脚本语言的支持。

11.Attr:实现来自Mangalore中动态属性的概念,Attributes的键/值对是编译安全的,因此提供了编译时检查Attributes名称和数据类型。Attributes是数据库子模块的基础。

12.Db:数据库子系统是Mangalore数据库子系统的升级版本。为存储应用程序不同的数据类型提供一个抽象的,高效的SQL数据库接口。使用SQLite作为标准的后台数据库实现,为了获得数据库最优的性能该代码已经被仔细调整过。(在最新版本中该子系统已经被移出基础层)

13.Naming:(在最新版本中该子系统已被删除)

原文: Nebula3 Foundation Layer Overview

[声明]:限于译者水平,文中难免错漏之处,欢迎各位网友批评指正;

Nebula3架构概述

1.Nebula3将被分为3层,每一层都是建立在其它层上面的:

基础层:位于最底层,为渲染和音频提供一个平台抽象。基础层也可以为其它应用程序提供 一个平台抽象而不仅仅是实时3D应用程序。

渲染层:位于中间层,在基础层上面增加了更多的东西,像3D渲染,音频,物理和场景管理。

应用成:位于最上层,提供一个完整的游戏框架让开发人员专注于游戏逻辑而不是其它细节。

2.Nebula3将完全集成Mangalore,Mangalore的各个子系统将被合理地集成到Nebula3的各层中。

3.Nebula3比Nebula2更趋向于使用C++。

4.Nebula3将使用引用计数和智能指针实现对象的生命周期管理。

5.Nebula3新的对象模型使用一个4 byte的基类来代替Nebula2中70+ bytes的。

6.高效,易用的动态运行时。

7.Nebula3仍然不使用c++异常,c++动态运行时和标准模板库(所有这些不是降低性能就是降低移植性)。

8.快速,简单地使用类名创建对象。

9.Nebula3将保持c函数库的简洁,复杂的c函数库方法将是不允许的(文件IO或者是多线程),去掉多余的代码层。

10.Nebula3将使用LUA作为它标准的脚本语言,替代Nebula2中的TCL(当然也可以增加其它脚本语言的支持)。

原文: Nebula3 Architecture Overview

[声明]:限于译者水平,文中难免错漏之处,欢迎各位网友批评指正;

从Nebula2到Nebula3

为什么需要Nebula3:

1.Nebula2主要是重写了Nebula1的高层代码。核心代码和底层代码从Nebula1开始就没有很大的改动,这表明Nebula1的一些底层代码距今已经有八年了。

2.一些Nebula2特性在当时看起来很酷但在现在已经不适宜了(至少对于Radon Labs而言)。例如可以在运行时切换OpenGL渲染或者D3D渲染,细粒度的脚本支持,等等......

3.更多现实世界的开发经验让我们能更合理地安排某些子系统,并在Nebula的层次模型中选择是上移还是下移。

4.初学者很难掌握Nebula,部分是由于它深奥的对象模型和其它设计决策引起的。并且经验表明,应用程序员使用高层游戏框架比直接使用Nebula来得容易。因此,Nebula更多的是为高层游戏框架提供平台抽象服务。Nebula3将关注这方面变化。

5.Nebula2很难向上或向下移植(向上移植对多核和DirectX10支持,向下移植到Nintendo DS)。

6.更好的多线程架构。Nebula3一开始就被设计运行在多核硬件上,并提供一个编程模型让程序员不必要太关注多核环境。

7.更好的网络架构。网络子系统是事后才被集成到Nebula2中的。Nebula3将从一开始就提供对网络支持。

8.Nebula2没有提供一个适当的高层游戏框架,这就是为什么我们要写Mangalore。这种方式造成了很多的混淆,Nebula3将一开始就设计成三层架构,在最高层将提供一个完整的游戏应用框架,将把Mangalore集成到Nebula3中。

原文: From Nebula2 to Nebula3

[声明]:限于译者水平,文中难免错漏之处,欢迎各位网友批评指正;

Nebula Device现状

Nebula Device现状

Nebula2现状:

1.Nebula2现在很稳定,这意味着Nebula2的基础代码除了维护,优化和移植外不会有很大的变动。

2.近期我们为Nebula2增加了对XACT的支持。

3.开发的重心将转移到Mangalore(我们一个高层的游戏框架)和Nebula3(Nebula Device的下一个版本)。

4.我们设计了一个Nebula2的精简版(叫做Nebula Embedded),并移植到Nintendo DS(内部称为Nebula DS)。

5.Nebula3将会有3层架构,核心系统和图形系统将被完全重写,Mangalore将做为应用层集成进来。

6.关于Nebula3更详细的信息请留意该博客

原文:Current state of the Nebula Device

[声明]:限于译者水平,文中难免错漏之处,欢迎各位网友批评指正;