2009年5月2日星期六

2009年4月3日星期五

Nebula3基础层-App


1.控制台程序需要继承ConsoleApplication类,如果需要做一些初始化可以覆写ConsoleApplication::Open()方法,在覆写该方法时要记得调用父类相同的方法来初始化。程序的逻辑代码可以在ConsoleApplication::Run()方法中实现。

2.ConsoleApplication::Open()方法的主要是初始化核心子系统,IO子系统和脚本系统。

Nebula3基础层-IoInterface

从类图上我们可以看出,通过扩展消息模块我们很轻松地让IO子系统运行在一个单独的线程中。其它子系统通过发送消息来和它进行交互。

2009年4月2日星期四

Nebula Device Tutorials

http://www.geocities.com/chicoze2/

2009年3月31日星期二

Nebula3基础层-消息子系统


Message子系统是Nebula3实现多线程架构的核心。

Port:1.消息接收端口。消息被立即处理并且程序将被阻塞直到消息处理完成。消息是通过增加到Port中的Handler来处理的,Port可以增加一个或多个Handler。当接收到一个消息,增加到Port中的每个Handler将被调用来处理消息,直到有一个Handler处理完消息后返回true,这意味着消息处理完成了。

2.Port::RegisterMessage(const Id& msgId)方法用来注册该Port能处理的消息类型。

AsyncPort:在一个分开的线程中调用handlers来处理消息,因此不会阻塞主线程。AsyncPort的子类覆写AsyncPort::OnCreateHandlers()方法,在这个方法中创建Handler并把它添加到AsyncPort中。

Handler:实际处理消息的类。用户需要继承Handler类并覆写Handler::HandleMessage()方法。

Id:消息类型标识符。在消息中使用DeclareMsgId和ImplementMsgId宏来实现。

Message:包装数据并可以被发送到Port和AsyncPort。这实现了一个通用的通信机制,不仅可以用于相同线程,线程之间,甚至还可以用在不同机器之间的通讯。Message作为一个普通的C++对象,可以被持久化。用户继承Message类实现自己需要的不同消息。

Dispatcher:是一个特殊的消息接收端口。Dispatcher把消息分发到能处理该消息的端口上。具体实现可以参考类图和以下图示:

Nebula3基础层-线程子系统


线程子系统主要是对各种平台的多线程部分进行抽象和封装。

Thread:对平台线程的封装,调用Thread::Start()开启一个新的线程。用户需要在子类覆写Thread::DoWork()方法来执行自己需要的操作,当用户需要在子类的DoWork()方法中执行循环操作时就必须调用Thread::ThreadStopRequested()方法来判断线程是否停止。调用Thread::Stop()停止一个线程,线程会等待DoWork()方法处理完成后才退出。

Event:用来处理线程同步。当一个线程调用Event::Wait()时,线程将处于等待状态。调用Event::Signal()将激活一个等待的线程。Event::Peek()用来检查线程当前的状态,并立即返回结果。

CriticalSection:同步代码块,处于CriticalSection::Enter()和CriticalSection::Leave()之间的代码,同时只能由一个线程来执行。

Interlocked:提供一些简单的原子操作。

2009年3月30日星期一

Nebula3基础层-IO子系统


Io子系统的整个设计主要是参考了.net的io设计。一个比较重要的设计是对路径的抽象。

Stream:一个抽象的数据流,接着分别实现了文件流和内存流,zip流。用户可以根据自己的需要实现其它类型的流。

StreamReader,StreamWriter:提供了读写流的抽象。

FSWrapper:包装了不同平台的io操作。

IoServer:Io子系统对外提供服务的接口,主要有以下操作:

1.根据不同的URI scheme关联到不同的流类。
2.根据给定的URI创建正确的流。
3.对于ZIP压缩包提供透明的支持。
4.路径别名管理。
5.提供全局的文件系统操作和查询方法。

2009年3月6日星期五

Lua程序设计 第九章 协同程序

1.协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈,局部变量和指令指针,同时又与其它协同程序共享全局变量和其它大部分东西。

2.线程与协同程序的主要区别在于,一个具有多线程的程序可以同时运行几个线程,而协同程序却需要彼此协作地运行。就是说,一个具有多个协同程序的程序在任何时刻只能运行一个协同程序,并且正在运行的协同程序只会在其显示地挂起时,它的执行才会暂停。

3.当一个协同程序A唤醒另一个协同程序B,协同程序A就处于一个特殊状态,既不是挂起状态(无法继续A的执行),也不是运行状态(B在运行)。所以将这时的状态称为“正常”状态。

4.Lua提供的是一种“非对称的协同程序(asymmetric coroutine)”。也就是说,Lua提供了两个函数来控制协同程序的执行,一个用于挂起执行,另一个用于恢复执行。而一些其它的语言则提供了“对称的协同程序(symmetric coroutine)“,其中只有一个函数用于转让协同程序之间的执行权。

Lua程序设计 第八章 编译,执行与错误

1.区别解释型语言的主要特征并不在于是否能编译它们,而是在于编译器是否是语言运行时库的一部分,即是否有能力执行动态生成的代码。

2.Lua提供的所有关于动态库的功能都聚集在一个函数中,即package.loadlib。

3.由于Lua是一种扩展语言,通常嵌入到应用程序中,因此在发生错误时不能简单地崩溃或退出。相反,只要发生一个错误,Lua就应该结束当前程序块并返回应用程序。

4.对于大多数应用程序而言,无须在Lua代码中作任何错误处理,应用程序本身会负责这类问题。

2009年3月5日星期四

Lua程序设计 第六章 深入函数

1.Lua支持匿名函数。

2.函数存储在table字段中可以支持许多Lua的高级应用,例如:模块和面向对象编程。

3.若将一个函数写在另一个函数中,那么这个位于内部的函数便可以访问外部函数中的局部变量,这项特性称之为“词法域”。

4.函数不仅可以存储在全局变量中,还可以存储在table的字段中和局部变量中。

5.Lua函数支持“尾调用消除(tail-call elimination)”。所谓“尾调用”就是一种类似goto的函数调用。当一个函数调用是另一个函数的最后一个动作,该调用才算是一条“尾调用”。在进行“尾调用”时不耗费任何栈空间。

Lua程序设计 第五章 函数

1.在Lua中,函数是一种对语句和表达式进行抽象的主要机制。

2.一个函数若只有一个参数,并且此参数是一个字符串或table构造式,那么圆括号便可有可无。

3.调用时提供的实参数量可以与形式参数数量不同。Lua会自动调整实参的数量,以匹配参数表的要求。原则是:若实参多于形参,则舍去多于的实参;若实参不足,则多余的形参初始化为nil。

4.Lua允许函数返回多个结果。

5.return后面的内容是不需要圆括号的。在该位置上书写圆括号会导致不同的行为。例如:return(f(x)),将只返回一个值,而无关f返回几个值。

6.Lua中的函数还可以接受不同数量的参数。

7.Lua用...参数表式函数可以接受不同数量的实参。

8.Lua中的参数传递机制是具有“位置性”的,但有时通过名称来指定实参也是很有用的。

2009年3月3日星期二

Lua程序设计 第四章 语句

1.Lua允许多重赋值,也就是将多个值赋予多个变量。

2.Lua用local语句来创建局部变量。

3.尽可能地使用局部变量,局部变量可以避免将一些无用的名称引入全局环境,避免搞乱了全局环境。

4.在需要时才声明变量,可以使这个变量在初始化时刻就拥有一个有意义的初值,缩短变量的作用域有助于提高代码的可读性。

Lua程序设计 第三章 表达式

1.算术操作符:“+”(加法),“-”(减法),“*”(乘法),“/”(除法),“^”(指数),“%”(取模),“-”(负号)

2.关系运算符:“<”(小于),“>”(大于),“<=”(小于等于),“>=”(大于等于),“==”(相等性测试),“~=”(不等性测试)

3.逻辑操作符:and,or,not

4.Lua中使用操作符“..”(两个点)连接两个字符串。

5.构造式是用于创建和初始化table的表达式。

2009年3月2日星期一

Lua程序设计 第二章 类型与值

1.Lua在条件测试中,将数字0和空字符串视为“真”。

2.Lua能够高效处理长字符串,在Lua程序中操作100k或1M的字符串是很常见的。

3.Lua提供运行时的数字与字符串的自动转化,但到现在还不能确定在Lua的设计中,这些自动转化是否是算一项好的设计,建议最好不要依赖它们。

4.定义a为table,a.x和a[x]是不同的,前者表示a["x"],以字符串"x"来索引table。而或者是以变量x的值来索引table。

5.就Lua习惯而言,数组通常以1作为索引的起始值。

6.在Lua中,函数是作为“第一类值”来看待的。这表式函数可以存储在变量中,可以通过参数传递给其它函数,还可以作为其它函数的返回值。

7.userdata用于表式一种由应用程序或C语言库所创建的新类型。

2009年2月28日星期六

开始使用Twitter

把关于游戏开发的一些心得,体会记录起来。

https://twitter.com/talknebula

2009年2月26日星期四

Generating Shaders From HLSL Fragments

Shaders are cool. You can do all sorts of interesting things with them: this and previous ShaderX books are full of examples. Alongside their power, however, programmable shaders can lead to an explosion of permutations: my last Xbox game contained 89 different pixel shaders, and my current project already has far more. Many of these shaders are variations on a few basic themes, for instance level-of-detail approximations of a material, or the same lighting model both with and without animation skinning. The total number of combinations is huge and is increasing all the time. Typing everything out by hand would be time consuming, error prone, and a maintenance nightmare.

This article will describe how to automatically generate large numbers of shader permutations from a smaller set of handwritten input fragments.

全文:Generating Shaders From HLSL Fragments

2009年2月23日星期一

Nebula3的点和向量

在Nebula3中严格区分点和向量并分别使用point和vector来表示点和向量。point和vector都是继承float4,两者的区别在于point的w分量为1,而vector的w分量为0。在Nebula3中点和向量分别提供了一些直观的操作:

点:

点+向量=点
点-向量=点
点-点=向量

向量:

向量+向量=向量
向量-向量=向量
向量*常量=向量

Nebula3使用的一些关键字

__forceinline:

在VC++中可使用另一关键字_forceinline 代替inline 关键字.这个关键字将命令编译器跳过一般的ROI 分析(Return On Investment --一种编程缩略语),将所对应的代码强行内联.在有写时候,编译器会拒绝将一个函数内联,使用这个关键字,用户只得到一个编译警告,就可强行内联.

在使用内联函数时,是由编译器决定它们是按普通函数处理还是将调用函数部分用实际的函数体代码替换。不允许将递归函数进行内联(VC++可进行编译器选项设置,允许内联扩展到一定深度)

下面情况不宜使用内联:

1.如果函数体内的代码比较长,使用内联将导致内存消耗代价较高。
2.如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。

volatile:

如果将将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化。

一般说来,volatile用在如下的几个地方:

1、中断服务程序中修改的供其它程序检测的变量需要加volatile。
2、多任务环境下各任务间共享的标志应该加volatile。
3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义。

__cdecl:

C,C++的默认调用规范,参数从右到左传替,由调用函数管理堆栈。

2009年2月19日星期四

Nebula3单例

很多重要的Nebula3对象都是单例,在应用程序中这些单例仅仅存在一次并且可以被其它对象所获取。

可以通过静态的Instance()方法获得单例对象,该方法返回单例类的单一实例。保证返回的指针是有效的。在Instance()方法被调用时候如果单例对象不存在,那么将抛出一个断言。

// obtain a pointer to the Core::Server singleton
Ptr = Core::Server::Instance();

你也可以检查给定的单例是否存在:

// does the Core::Server object exist?
if (Core::Server::HasInstance())
{
// yep, the core server exists
}

Nebula3提供了一些宏帮助实现单例类:

// declare a singleton class
class MySingletonClass : public Core::RefCounted
{
DeclareClass(MySingletonClass);
DeclareSingleton(MySingletonClass);
public:
/// constructor
MySingletonClass();
/// destructor
virtual ~MySingletonClass();
...
};

// implement the singleton class
ImplementClass(MyNamespace::MySingletonClass, 'MYSC', Core::RefCounted);
ImplementSingleton(MyNamespace::MySingletonClass);

//------------------------------------------------------------------------------
/**
Implements the Singleton constructor.
*/
MySingletonClass::MySingletonClass()
{
ConstructSingleton;
}

//------------------------------------------------------------------------------
/**
Implements the Singleton destructor.
*/
MySingletonClass:~MySingletonClass()
{
DestructSingleton;
}

DeclareSingleton()和ImplementSingleton()宏跟DeclareClass()和ImplementClass()宏相似。它们往类里增加了一些静态方法(Instance()和HasInstance()方法)。类的构造函数和析构函数必须包含ConstructSingleton和DestructSingleton宏。ConstructSingleton宏初始化一个私有的静态单例指针并确定这个类没有其它的实例存在(否则,将抛出一个断言)。DestructSingleton宏让静态单例指针无效。

默认是从本地线程获取一个单例。这意味着单例是创建在Nebula3应用程序的一个线程中,并且其它线程不能访问该单例。这种方式遵循着“并行Nebula”的设计,让多线程编程变得更加简单。隐藏在“并行Nebula3”背后的构想是,一个典型的Nebula3应用程序包含一些运行在分开的CPU核上的“胖线程”("Fat Threads")。胖线程实现例如异步IO,渲染,物理等等。每一个胖线程初始化它自己的Nebula3运行库,这个库包含胖线程执行特定任务所需的最小Nebula3环境。这基本上消除了大部分Nebula3代码所需的细粒度同步,并且把“线程相关”的代码集中在几个定义明确的范围内用于胖线程之间的通讯。“并行Nebulas”设计的另外一个有用效果是,让程序员不必太关注代码运行在一个多线程环境。典型的Nebula3代码就像普通的单线程代码,然而它可以运行在它自己的胖线程内。

原文:<<The Nebula Device 3 Document>>Nebula3 Singletons

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

Nebula3运行时类型信息系统

Nebula3的RTTI系统允许你在运行时取得一个对象类类型并让你检查一个对象是否正是一个类的实例,或者是继承类的实例。你也可以直接从一个对象中取得类名或类fourcc标识符。所有这些功能都是在DeclareClass()和ImplementClass()背后实现的。Nebula3的RTTI机制比Nebula1和Nebula2的RTTI机制要来得更高效和更简单。

例子:

using namespace Util;
using namespace Core;

// check whether an object is instance of a specific class
if (myObj->IsInstanceOf(MyClass::RTTI))
{
// it's a MyClass object
}

// check whether an object is instance of a derived class
if (myObj->IsA(RefCounted::RTTI))
{
// it's a RefCounted instance or some RefCounted-derived instance
}

// get the class name of my object, this yields "MyNamespace::MyClass"
const String& className = myObj->GetClassName();

// get the fourcc class identifier of my object, this yields 'MYCL'
const FourCC& fourcc = myObj->GetClassFourCC();

你也可以通过中心工厂对象查询给定的类是否注册了:

using namespace Core;

// check if a class has been registered by class name
if (Factory::Instance()->ClassExists("MyNamespace::MyClass"))
{
// yep, the class exists
}

// check if a class has been registered by class fourcc code
if (Factory::Instance()->ClassExists(FourCC('MYCL')))
{
// yep, the class exists
}

原文:<<The Nebula Device 3 Document>>The Nebula3 Runtime Type Information System

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

2009年2月18日星期三

创建Nebula3对象

从Core::RefCounted继承下来的Nebula3对象可以通过3中不同的方式创建:

1.直接使用静态创建方法:

Ptr< myObj> = MyClass::Create();

静态Create()方法是在声明的前面通过DeclareClass()宏添加到类中的。这是C++ operator::new()的语法糖衣。实际上,Create()方法内部除了调用new操作以外什么也没有。另外,正确使用智能指针持有新对象。

2.另外一种方式是使用类名创建对象:

using namespace Core;

Ptr< myObj> = (MyClass*) Factory::Instance()->Create("MyNamespace::MyClass");

如果你在编译时不知道对象类,通过它的字符串类名创建一个对象是很有用的。这种方式常常用于恢复序列化对象,或者使用某种脚本接口。注意类型转换,因为Create()方法返回一个通用的指向Core::RefCounted对象的指针。

3.通过类的fourcc类标识符创建对象:

using namespace Core;
using namespace Util;
Ptr<> = (MyClass*) Factory::Instance()->Create(FourCC('MYCL'));

这种方式看起来比较不直观,但它创建对象的速度比使用类名快,并且fourcc类标识符(4bytes)比字符串类名占用更少的空间。当一个对象被编码/解码到二进制流的时候这种方式创建对象方式有很多的优点。

原文:<<The Nebula Device 3 Document>>Creating Nebula3 Objects

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

引用计数和智能指针

Nebula3使用传统的引用计数来管理对象的生命周期。从程序员的角度来看一个模板智能指针类Ptr<>的存在隐藏了引用计数的细节。做为一般规则,总是使用智能指针指向从RefCounted继承的对象,除非你可以确定在给定的代码块,对象的引用计数不会被改变。

智能指针比普通指针具有的优点:

1.获取一个空指针时,智能指针将给你一个简单调试断言而不是内存错误
2.在引用计数对象中你从不必调用AddRef()或者Release()方法(实际上如果你调用了,会有一些严重的错误)
3.智能指针可以很好地工作在容器类内,一个智能指针数组代替普通指针消除了各种生命周期管理问题,你从不需要关心指针后面对象的释放,数组的行为看起来像包含真的C++对象
4.使用智能指针,你一般不需要像普通指针那样经常定义“对象所属关系”(谁要负责删除对象,等等...)

智能指针的缺点:

1.性能:拷贝和赋值智能指针时包含调用AddRef()和/或Release()方法,间接引用一个智能指针包含一个检查智能指针所包含的对象指针是否有效的断言检查。由此产生的性能问题一般被忽略,但在内部循环你必须意识到这一点。

2.原本要销毁的对象还一直存在着:由于使用智能指针管理对象,只有当最后一个客户放弃了所有权后对象才会被删除,对象可能比预期存在更长时间。往往这是一个错误点。Nebula3将提示你有关任何的引用计数泄露。

原文:<<The Nebula Device 3 Document>>RefCounting And Smart Pointers

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

2009年2月17日星期二

实现一个新的Nebula3类

当实现一个新类时要做的第一个决定是该新类是继承Core::RefCounted类或者是一个传统的C++类。下面几点将有助于你找到答案:

1.如果新类打算使用Nebula3对象模型扩展的特性如refcounting,RTTI等等,那么它必须继承Core::RefCounted类。

2.如果新类是一个典型很小的帮助类或工具类,像一个动态数组类,一个数学向量类,或者其它类似的。那么它从Core::RefCounted继承下来也没有什么意义。

继承Core::RefCounted的类会有一些限制:

1.继承Core::RefCounted的类不能直接在本地C++上下文创建栈对象,因为栈对象的生命周期是由C++管理的(当离开当前C++上下文它们将自动销毁,这样就完全绕开了Nebula3的引用计数生命周期管理)。

2.继承Core::RefCounted的类只有一个默认的构造函数。

3.继承Core::RefCounted的类必须有一个虚拟的析构函数。

4.继承Core::RefCounted的类必须不能拷贝,因为这样将搞乱引用计数机制。

为了使用Nebula3对象模型特性,首先是要继承Core::RefCounted类,其次是要在新类的声明和头部文件注释一些额外的信息:

一个标准继承Core::RefCounted类的声明看起来像这样:

namespace MyNamespace
{
class MyClass : public Core::RefCounted
{
DeclareClass(MyClass);
public:
/// constructor
MyClass();
/// destructor
virtual ~MyClass();
...
};
RegisterClass(MyClass);

注意DeclaredClass()宏,默认构造函数和虚拟析构函数和在类声明外的RegisterClass()宏。DeclareClass()宏为实现RTTI和工厂机制在类声明中增加了一点点Nebula3特性信息。从程序员的角度来看DeclareClass()宏通常是隐藏在Nebula3对象模型内部,因此,对象模型内部可以在不影响已存在类的情况下被改变。RegisterClass()宏是可选的,它注册类到中心工厂对象。如果你知道类对象将永远不会通过字符串类名或fourcc代码创建,那么RegisterClass()宏可以被忽略。

在类的.cc文件中需要包含以下Nebula3特殊信息:

namespace MyNamespace
{
ImplementClass(MyNamespace::MyClass, 'MYCL', Core::RefCounted);
}

ImplementClass()宏注册了类的RTTI机制,第一个参数是C++类名(注意,类名必须包含命名空间)。第二个参数是的类的fourcc代码,fourcc代码在所有类中必须是唯一的(在程序启动时如果有2个类注册了相同的fourcc代码将会产生一个运行时错误)。第三个参数是父类的名称。这个被RTTI机制用来重建类树。

(说明:在N3的最新版本中DeclareClass改为__DeclareClass,ImplementClass改为__ImplementClasClass改为__RegisterClass)

原文:<<The Nebula Device 3 Document>>Implementing A New Nebula3 Class

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

Nebula3对象模型

Nebula3实现了基本对象模型,该对象模型在c++对象模型的基础上实现了以下几个新的特性:

1.通过引用计数和智能指针实现对象生命周期管理
2.通过字符串类名或fourcc类标识符创建对象
3.一个运行时类型信息系统

原文:<<The Nebula Device 3 Document>>The Nebula3 Object Model

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

2009年2月16日星期一

Nebula3核心子系统

Nebula3核心子系统(顾名思义)实现了Nebula3的核心概念,如下:

1.一个RefCounted基类用于实现一个强大的引用计数机制
2.一个运行时类型信息系统
3.一个模板智能指针类Ptr<>用于管理RefCounted对象的声明周期
4.一个工厂机制,允许使用字符串类名创建c++对象
5.一个中心服务对象用于设置基本的Nebula3运行环境

原文:<<The Nebula Device 3 Document>>The Nebula3 Core Subsystem

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

Python查找并替换字符串

Python字符串提供了replace(old,new,maxreplace)方法,用新的文本替换指定的子字符串。replace方法接收一个查找字符串作为第一个参数,替换字符串作为第二参数。每一个查找到的字符串都将用一个新的字符串替换掉。作为可选,你可以指定一个最大的执行替换操作次数的数字作为第三个参数。

question = "What is the air speed velocity of \
an unlaiden swallow?"
print question
question2 = question.replace("swallow", \
"European swallow")
print question2
question3 = question.replace("swallow", \
"African swallow")
print question3

replace_str.py

What is the air speed velocity of an unlaiden
swallow?
What is the air speed velocity of an unlaiden
European swallow?
What is the air speed velocity of an unlaiden
African swallow?

Output from replace_str.py code

原文:<< Python Phrasebook: Essential Code and Commands>> Search and Replace in Strings

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

Python查找子字符串

Python提供find(sub, [, start, [,end]]))和index(sub, [, start, [,end]])这两种最常见的方法查找字符串中的子字符串。

index方法的查找速度比find快;然而,如果在字符串中没有找到所需的子字符串,index方法将抛出一个异常。如果find没有找到所需的子字符串,那么它将返回-1。find和index方法接受要查询的字符串作为它的第一个参数。被查找的字符串范围可以通过具体指定可选的开始和/或结束索引限制。只有在这索引之间的字符才会被查找。

Python也提供了rfind和rindex方法。这两个方法的工作方式和find和index很像。然而,它们是从字符串的右边开始查找子字符串。

searchStr =
"Red Blue Violet Green Blue Yellow Black"

print searchStr.find("Red")
print searchStr.rfind("Blue")
print searchStr.find("Blue")
print searchStr.find("Teal")
print searchStr.index("Blue")
print searchStr.index("Blue",20)
print searchStr.rindex("Blue")
print searchStr.rindex("Blue",1,18)

search_str.py

0
22
4
-1
4
22
22
4

Output from search_str.py code

原文:<< Python Phrasebook: Essential Code and Commands>> Searching Strings for Substrings

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

2009年2月15日星期日

Python拆分字符串

Python提供split(separator)和splitlines(keeplineeds)方法用于拆分字符串。split方法查找字符串,并根据分隔符拆分,把拆分好的子字符串放入字符串列表中。如果没有具体指定分隔符,split方法将使用空格来拆分。

splitlines方法根据换行符拆分字符串,并把结果放入字符串列表。当你解析很大文本的时候这将是很有用的。splitlines方法接收一个布尔型的参数决定换行符是否被保留。

sentence = "A Simple Sentence."

paragraph = "This is a simple paragraph.\n\
It is made up of of multiple\n\
lines of text."

entry =
"Name:Brad Dayley:Occupation:Software Engineer"

print sentence.split()
print entry.split(':')
print paragraph.splitlines(1)

split_str.py

['A', 'Simple', 'Sentence.']
['Name', 'Brad Dayley', 'Occupation',
'Software Engineer']
['This is a simple paragraph.\n',
'It is made up of of multiple\n',
'lines of text.']

Output from split_str.py code

原文:<< Python Phrasebook: Essential Code and Commands>> Splitting Strings

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

Python连接字符串

在Python中可以使用简单的加法操作,格式化字符串连接或者join()方法来连接字符串。使用+或+=是比较简单的实现连接方式。

格式化字符串连接是通过定义一个带有格式化代码%s的新字符串来实现,并使用额外的字符串作为参数去填充每个字符串格式化代码。这个可能非常有用,特别当要连接一个复杂格式的字符串时。

连接一个字符串列表的最快方式是使用join(wordList)方法,在列表中的每个字符串将被连接在一起。join方法有一点点的特别因为它本质是迭代字符串列表并执行string+=list[x]操作。这样的结果是字符串将被增加到每个列表元素的前面。如果你想在列表中的词之间增加一个空格这个将变得非常有用,因为你只要简单地定义一个包含一个空格的字符串并实现join方法就可以了:

word1 = "A"
word2 = "few"
word3 = "good"
word4 = "words"
wordList = ["A", "few", "more", "good", "words"]

#simple Join
print "Words:" + word1 + word2 + word3 + word4
print "List: " + ' '.join(wordList)

#Formatted String
sentence = ("First: %s %s %s %s." %
(word1,word2,word3,word4))
print sentence

#Joining a list of words
sentence = "Second:"
for word in wordList:
sentence += " " + word
sentence += "."
print sentence

join_str.py

Words:Afewgoodwords
List: A few more good words
First: A few good words.
Second: A few more good words.

Output from join_str.py code

原文:<<Python Phrasebook: Essential Code and Commands> >Joining Strings

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

2009年2月14日星期六

Python比较字符串

在Python中比较字符串最好是使用简单逻辑操作符。例如,确定一个字符串是否和另外一个字符串匹配。正确的,你可以使用is equal或==操作符。你也可以使用例如>=或<来确定几个字符串的排列顺序。

Python字符串对象提供了几个方法用来帮助字符串比较。最经常使用的是upper()和lower()方法,它们将分别返回一个新的小写字母字符串和新的大写字母字符串。

另外一个有用的方法是capitalize(),它返回一个第一个字母是大写的新字符串。还有一个swapcase()方法,返回一个新的字符串,并且每个字符的大小写刚好跟原来相反。

cmpStr = "abc"
upperStr = "ABC"
lowerStr = "abc"

print "Case Sensitive Compare"
if cmpStr == lowerStr:
print lowerStr + " Matches " + cmpStr

if cmpStr == upperStr:
print upperStr + " Matches " + cmpStr

print "\nCase In-Sensitive Compare"
if cmpStr.upper() == lowerStr.upper():
print lowerStr + " Matches " + cmpStr

if cmpStr.upper() == upperStr.upper():
print upperStr + " Matches " + cmpStr

comp_str.py

Case Sensitive Compare
abc Matches abc

Case In-Sensitive Compare
abc Matches abc
ABC Matches abc

Output from comp_str.py code

原文:<<Python Phrasebook: Essential Code and Commands>> 2.1

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

2009年2月12日星期四

关于关卡设计和构建系统的思考(二)

整篇帖子的重点是:怎么样才能让关卡设计变得有趣呢?如果能让关卡设计师立即看到设计的结果那么他将会开心很多,并且还可以直接和其他的设计师一同工作。如果关卡设计可以像Wiki和多人游戏混合起来那会怎么样呢?

这是我们设想未来关卡设计的工作方式:

1.关卡设计师早上来上班,启动游戏到关卡设计模式。
2.游戏通知关卡设计师有需要更新,仅更新一个可执行文件。
3.游戏连接到中心游戏服务器,数据库中存储实际游戏数据,并且图形/音频内容通过网络共享。
4.关卡设计师直接在游戏中创建,放置和销毁游戏对象,所有这一些改变将通过游戏服务器分布到附近工作的其他关卡设计师。
5.为测试改动,关卡设计师按一下开始按键,几秒钟后,编辑器将转到游戏模式(严格区分编辑模式和游戏模式是很重要的,因为应用程序员不关心关卡设计器的东西)。
6.内置游戏编辑器是用C#写的专门工具窗体。
7.晚上,关卡设计师关掉机器并回家。

因此我们将放弃Maya作为关卡设计工具并赞同使用“collaborative ingame level editor”。这个协助/多人部分听起来有点像骗人的玩意,但它实际上是很重要的因为它可以解决数据冲突问题。由于所有的改变可以立即分布到所有的关卡设计师那里,创建相冲突的数据就没有什么危险了。

直到几天前我放弃了整个设想并宣布这是不可能实现的。实现一个适合所有不同类型的内置游戏编辑器听取来是一件很困难的事情。但到最后它也不是那么困难。我们已经有许多的基础模块可用:

1.我们可以从我们当前的“Remote Level Design”获得很多想法。目前,我们可以一边运行Maya一边运行游戏,在Maya中的改变可以立即在游戏中显示,例如这对于调节灯光参数是很有用的。

2.游戏数据已经完全存储在一个轻量本地数据库中(SQLite)。这给了我们很多优势:

2.1一个游戏实体通过一个简单的命名和类型属性就可以完整描述
2.2一个游戏实体在数据库表中总是占据一行
2.3所有“其它数据”已经存储在数据库表中
2.4所有数据的操作可以用一个很小的SQL子集来表式(INSERT,UPDATE and DELETE ROW)

3.The only operations that must be supported by the generic ingame level editor must be "Navigate", "Create Entity", "Update Entity", "Destroy Entity", where creation is always a duplication either from a template or from another entity. More complex operations, or different views on the game data, will be implemented in C# tools which are connected through a standardized plugin interface.

4. 使用Nebula3的TcpServer/TcpClient类和IO子系统作为基础将比较容易实现游戏中客户/服务系统的需求

5.我们已经使用一些用C#写的专门编辑工具(之前我们是用MEL来写的,使用C#来开发用户图形界面比MEL有很大的效率提高)

当然了魔鬼总是在细节中。但我想这是一个不错的计划,在将来的项目上从根本改善我们关卡设计流程。

原文:Level Design And Build System Thoughts

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

关于关卡设计和构建系统的思考(一)

最近我经常和Bernd讨论如何在接下来几个月改善我们的构建系统和关卡设计流程。对于一个“大”的项目如Drakensang,完整的构建周期和增量构建周期,还有关卡设计人员的“local turnaround”开始变得很重要了。一个完整(每夜)构建现在大约要11小时(包括重新编译和重新构建每一样东西,生成一个安装包并把结果上传到FTP站点上)。一个增量构建(在白天)要花费至少半个小时到2个小时(没有生成安装包和上传)。进一步看,Drakensang大约有7000个纹理和大约4500到5000个3D模型(因为我现在不在radon Labs所以不知道确切的数字),整个游戏的运行数据现在大约4GB大小。

对于关卡设计师,这里有两个分开的时间周期问题:从最近的每夜构建中更新工作机的数据(这可能需要半小时到一个小时),和在实际游戏中测试改动的时间(我们现在使用Maya作为关卡设计器,而不是一个游戏内置的编辑器)。

在Radon Labs我们有一些规定:

1.每日构建:每个人必须工作在最新的数据上,至多不能超过一天。
2“Make Game:在中心构建机器上创建一个完全自动的完整构建。
3.The Toyota Rip Cord(不知道这个翻译是否正确,在德文是“Toyota Reißleine”):如果不能构建,生产必须被停下来,直到问题被找到并解决掉。
4.一个工作只使用一件工具:对于相同的工作不能使用几个不同的工具(例如所有的3D模型都是使用Maya来做)。

我们还有其它一些规定,但它们对于构建系统或者关卡设计工作没有影响,因此我就不在这里介绍了:)

例如我们很容易因为害怕而放弃每日构建。但这将很有可能在公司内部建立一个“象牙塔”。很多时候事情总是朝着这样的趋势发生,它们对于项目是有害的,在它们出现的时候必须制止住。

相反我们退回去并思考一下关于完美的构建系统和完美关卡设计系统看起来是什么样的。这整个问题可以分为3问题:

1.降低构建时间
2.分布构建数据到各个工作台
3.降低关卡设计师设计的周期时间

观点(1)是相对容易的。我想我们唯一能获得提高的是分布工作量到几台构建机器上。对于我们的Maya输出插件我们已经做了很多优化,因此不可能再有什么提高了。设置一个分布式构建系统是一项有趣的工作,如果你控制所有的构建工具这也不会太复杂。

观点(2)比较有趣。这里的问题是“我们真的需要把所有的构建数据分布到各个工作台吗?”每个工作台每天有4GB未压缩的数据,但一个具有代表性的关卡设计师每天正常的工作仅仅需要很少的数据,像下面这样:

1.关卡设计师早上来上班,从每夜构建中取得最近的构建数据。
2.关卡设计师cvs-edits他工作需要的文件。
3.关卡设计师使用Maya和几个专门的工具工作,像dialog and quest编辑器。
4.关卡设计师频繁的检入在游戏中他做的改动。
5.晚上,关卡设计师cvs-commits他的工作并回家。
6.构建机器开始一个完整的构建。

这里有几个问题:

1.在早上,很多时间浪费在更新工作机器上的运行数据。
2.在游戏中检查更改的时间周期太长了。
3.当关卡设计师每天晚上检入他的工作时,会和其他关卡设计师的工作发生难以意料的冲突。

根据具体项目的大小和复杂度,关卡设计变得越来越让人沮丧,因为越来越多的时间花在等待结果上。

原文:Level Design And Build System Thoughts

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

2009年2月10日星期二

Python Cheatsheet

Nebula3渲染层:图形核心系统(CoreGraphics)

图形核心子系统主要的功能是兼容包装主机3d渲染API。它被设计成在不损失功能或运行性能的前提下支持带有可编着色的Direct3D/OpenGL类型API。图形核心子系统的基本功能和Nebula2 gfx2-子系统一样,然而图形核心系统修复了许多Nebula2图形系统的问题。

乍一看,图形核心子系统因为有更多的类看起来比Nebula2复杂。这个原因很简单,因为图形核心系统的类更小并且更专门化。大多数图形核心系统的类的功能一句话就可以说清。而Nebula2中的每个类要同时处理几件事情,所以它的类个头大,数量少。

典型的Nebula3应用程序并不需要过多地与图形核心子系统打交道,而是与更高级的子系统,如图形子系统(我们将在下一个帖子详细讨论),发生关系。

图形核心子系统一些重要的设计目标:

1.无限任何条件就可以移植到Direct3D9,Direct3D10和Xbox360:

图形核心系统允许更自由地移植到其它平台,Nebula2使用虚拟方法的方式(porting-through-virtual-functions)进行移植,而图形核心子系统使用条件预定义(porting-by-conditional-typedefs),在不损失任何性能的情况下,移植可以自由地覆写任何一个类(例如:平台相关的方法可以做为内联方法)。

2.改善资源管理:

Nebula3将降低资源的使用和资源的初始化。通过ResourceLoader类初始化资源,保持实际的资源类小且紧凑,并且资源系统更加地模块化(想要弄清楚为什么必须要解决上述问题,看看Nebula2中的nTexture2类)。

3.减少集中化:

现在使用更多的专门的单例类代替一个大的nGfxServer2类:

RenderDevice: handles rendering of primitive groups to a render target。
DisplayDevice: 处理显示设置和显示管理。
TransformDevice:管理渲染需要的变换矩阵,处理视图矩阵,投影矩阵和模型矩阵的输入,并且提供矩阵的求逆和组合。
ShaderServer:着色系统的关键,下面有详细说明。

4.改善离屏渲染

5.更大地提升着色系统:

5.1 为渲染带有许多不同对象和材质的典型场景提供降低“切换和更新着色”费用的基础。

5.2 跟Nebula2一样,一个着色基本上是一个Direct3D效果(effect)(一族的技术(techniques),每种技术包含多个渲染过程,每个渲染过程包括多个渲染状态)。

5.3 ShaderInstances是带有自己着色参数值的效果(effect)拷贝。

5.4 现在更多的是通过ShaderVariables来设置着色参数。

5.5 ShaderVariations和ShaderFeature:通过特性位掩码一个着色可以提供不同专门变量以供选择。例如,一个特征可能叫做“Depth”,“Color”,“Opauqe”,“Translucent”,“Skinned”,“Unlit”,“PointLight”,一个着色可以为特征组合如“Depth | Skinned”,“Color | Skinned | Unlit”,“Color | Skinned | PointLight”,提供专门变量。高层渲染代码将视需要来设置特征位,并依赖当前的特征位掩码,在渲染时相应的着色变量将自动被选择。结合适当的工具,ShaderVariations和ShaderFeatures将对修复各种与可着色编程相关的维护和运行问题有很大的帮助。

相比较gfx2,图形核心系统的一些其它小改动是:

1.现在可以通过EventHandlers代替原来固定在图形系统处理DeviceLost/Restored和鼠标和键盘消息处理的方式。

2.VertexBuffer和IndexBuffer现在作为公开类。

3.Vertex组件现在支持压缩格式像Short2,Short4,UBYTE4N,等等...

4.DisplayDevice提供了几个便利的方法用于获得支持显示模式列表或当前桌面显示模式,且用于获得当前显示的详细信息(硬件,厂商和驱动的版本信息)。

5.现在可以在打开应用程序窗口之前调用静态的RenderDevice::CanCreate()方法实际检查当前主机是否支持3d渲染。

原文: The Nebula3 Render Layer: CoreGraphics

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

关于Nebula3的单例

Nebula3中有两种形式的单例:

1.使用__DeclareSingleton宏定义的单例,这是一种在线程中的单例。在每个线程中只有一个对象,但在整个应用程序中可能会有多个。

把__DeclareSingleton宏展开如下:

public:
ThreadLocal static type * Singleton;
static type * Instance() { n_assert(0 != Singleton); return Singleton; };
static bool HasInstance() { return 0 != Singleton; };

可以看见我们定义了一个由ThreadLocal修饰的静态属性,在types.h中,我们可以看到ThreadLocal是如下定义的:

#if __WIN32__
#define ThreadLocal __declspec(thread)

简单地说__declspec(thread)声明一个线程局部变量并具有线程存储时限,以便链接器安排在创建线程时自动分配的存储。

在Nebual3引擎中大量地使用这种形式的单例,这和整个的Nebula3的多线程架构设计相关。

2.使用__DeclareInterfaceSingleton宏定义单例,这就是全局单例了,在整个应用程序中只存在一个对象。

展开__DeclareInterfaceSingleton宏:

static type * Singleton;
static type * Instance() { n_assert(0 != Singleton); return Singleton; };
static bool HasInstance() { return 0 != Singleton; };

2009年2月9日星期一

Nebula3内存模块-内存池(二)

在上一篇blog中我们已经讨论完关于内存池的初始化,这里我们来看看是如何从内存池中取得所需的内存块。要从内存池中分配一块内存块,我们需要调用void* MemoryPool::Alloc()方法,接下来我们详细介绍该方法是如何执行的:

1.首先判断存放可分配内存块的内存页的数组roomyPages是否为空,如果为空就调用void MemoryPool::AllocPage()创建一个新的内存页。

if (this->roomyPages.IsEmpty())
{
// all pages are full, or no pages exist yet, need to allocate new page
this->AllocPage();
}

2.从roomyPages中取得一个内存页,并调用void* MemoryPoolPage::Alloc()方法实际分配一块内存块。

MemoryPoolPage* page = this->roomyPages.Back();
n_assert(page->GetNumFreeBlocks() > 0);

// note: the page pointer is guaranteed to be the one
// from the end of the roomyPages array!
void* ptr = page->Alloc();

3.判断刚才取得的内存页是否还有可以分配的内存块。如果没有就把它从roomyPages中删除掉。

if (0 == page->GetNumFreeBlocks())
{
this->roomyPages.EraseIndex(this->roomyPages.Size() - 1);
}

4.返回指向刚才分配的内存块的指针。

我们更加详细的看一下void* MemoryPoolPage::Alloc()是如何分配一个内存块的。

1.找第一个还没被使用的内存块,并把该内存块块头的pageObject指向本内存页,代码如下:

BlockIndex newBlockIndex = this->firstFreeBlockIndex;
BlockHeader* newBlock = this->BlockIndexToPointer(newBlockIndex);
n_assert(0 == newBlock->pageObject);
newBlock->pageObject = this;

2.设置该内存块的前置和后置,让已分配的内存块和未分配的内存块通过前置和后置关系形成两个逻辑上的列表。并让firstFreeBlockIndex索引指向未分配内存块逻辑列表的第一位,firstAllocBlockIndex索引指向已分配内存块逻辑列表的第一位。并且把当前已分配的内存块放在已分配逻辑列表的第一位。

this->RemoveBlockFromList(this->firstFreeBlockIndex, newBlockIndex);
this->InsertBlockIntoList(this->firstAllocBlockIndex, newBlockIndex);

3.移动指针,让指针指向内存块体。

void* dataPtr = (void*) (newBlock + 1);

4.把已分配内存块的记录数numAllocBlocks加1,返回指向内存块体的指针。

this->numAllocBlocks++;

以上就完成了从内存池中分配一个内存块。接下来我们探讨一下如何释放一个内存块。

我们调用void MemoryPool::Free(void* ptr)来释放一个从内存池分配的内存块。执行步骤如下:

1.通过传入的指针,我们找到分配该内存块的内存页。

MemoryPoolPage* page = MemoryPoolPage::GetMemoryPoolPageFromDataPointer(ptr);

2.调用内存页的void MemoryPoolPage::Free(void* ptr)来真正释放掉内存块。

3.如果该内存页包含一个可以分配的内存块就把该内存页放入roomyPages中。如果该内存页所有的内存块都没被分配,并且内存不止一个内存页,那么就把该内存页从内存池中删除掉。

if (page->GetNumFreeBlocks() == 1)
{
// we've been full previously, but are roomy now
this->roomyPages.Append(page);
}
else if (page->GetNumAllocatedBlocks() == 0)
{
// the page no longer contains any allocated blocks, free
// the entire page, unless this is the last page to
// prevent expensive allocation/deallocations if only
// one block is allocated/freed in this memory pool
if (this->pages.Size() > 1)
{
this->FreePage(page);
}
}

void
MemoryPool::FreePage(MemoryPoolPage* page)
{
n_assert(page->GetNumAllocatedBlocks() == 0);
IndexT pagesIndex = this->pages.FindIndex(page);
n_assert(pagesIndex != InvalidIndex);
IndexT roomyPagesIndex = this->roomyPages.FindIndex(page);
n_assert(roomyPagesIndex != InvalidIndex);

// note: we don't use EraseIndexSwap to keep the page order
// in the order of allocations
this->pages.EraseIndex(pagesIndex);
this->roomyPages.EraseIndex(roomyPagesIndex);
delete page;
}

同样接下我们看看内存页是如何释放掉内存块的void MemoryPoolPage::Free(void* ptr)。

1.取得要删除内存块的索引。

BlockHeader* block = ((BlockHeader*)ptr) - 1;
this->VerifyBlockHeader(block);
BlockIndex blockIndex = this->BlockPointerToIndex(block);

2.把该内存块块头pageObject指针设置为0。

n_assert(this == block->pageObject);
block->pageObject = 0;

3.把该内存块从已分配内存块的逻辑列表断开,并把它放入未分配内存块逻辑列表中。

this->RemoveBlockFromList(this->firstAllocBlockIndex, blockIndex);
this->InsertBlockIntoList(this->firstFreeBlockIndex, blockIndex);

4.已分配内存块的数量减1。

this->numAllocBlocks--;

以上就是n3内存池实现的一个大概讨论。

2009年2月8日星期日

The DirectX9.0 Direct3D Graphics Pipeline

Nebula3内存模块-内存池(一)

接下来我们来探讨一下关于n3内存池的实现。n3的内存池由两个类来实现MemoryPool和MemoryPoolPage。MemoryPool是线程安全的,而MemoryPoolPage却不是。

从大的结构上来讲,一个内存池(MemoryPool)中包含有多个内存页(MemoryPoolPage),每个内存页里面包含有多个的块(block),每个块包含有块头和内容。

n3的内存池是如何运作的呢?我们一步一步地来解释:

1.创建内存池,如果调用的是MemeoryPool默认的构造函数,那么我们还必须调用MemoryPool::Setup(const char*,SizeT,SizeT)方法来初始化内存池中的第一个内存页。如果我们使用另外一个相同参数的构造函数就不用了。代码如下:

MemoryPool::MemoryPool(const char* _name, SizeT _blockSize, SizeT _blocksPerPage) :
name(_name),
blockSize(_blockSize),
blocksPerPage(_blocksPerPage)
{
n_assert(_name != 0);
n_assert(_blockSize > 0);
n_assert(_blocksPerPage > 0);
this->AllocPage();
}

void
MemoryPool::AllocPage()
{
MemoryPoolPage* newPage = new MemoryPoolPage(this->blockSize, this->blocksPerPage);
this->pages.Append(newPage);
this->roomyPages.Append(newPage);
}

这边有三个参数,第一个说明内存池的名称,第二个是说明内存池中的内存页所包含的块的大小,第三个参数说明的内存池中的内存页所包含有几个块。创建完内存页之后把内存页放入一个包含内存页的pages数组:

this->pages.Append(newPage);

如果这个内存页有可以分配的内存块,那么该内存页同时也被放入另外一个roomyPages数组:

this->roomyPages.Append(newPage);

这样在分配内存块的时候,可以直接从roomyPages中取得内存页来分配内存块。

2.内存池是创建完了,那么里面的内存页是如何创建的呢?完整代码可以看MemoryPoolPage的构造函数。

第一步:计算实际内存块的大小:传入的内存块的大小+内存块头部的大小,并通过运算让实际内存块的大小正好是16的倍数。

SizeT blockSizeWithHeader = this->blockSize + sizeof(BlockHeader);
const int align = 16;
SizeT padding = (align - (blockSizeWithHeader % align)) % align;
this->blockStride = blockSizeWithHeader + padding;
n_assert(0 == (this->blockStride & (align - 1)));

第二步:计算一个内存页所需的总内存:实际内存块的大小*每个内存页有几个内存块。

this->pageSize = this->numBlocks * this->blockStride;

第三步:在PoolHeap类型的堆上分配实际需要的内存:

this->pageStart = (ubyte*) Memory::Alloc(Memory::PoolHeap, this->pageSize);
this->pageEnd = this->pageStart + this->pageSize;

第四步:设置内存块的头部信息:

内存块的头部信息如下定义:

struct BlockHeader
{
// keep size of this structure at 16 bytes!
uint headerStartCanary; // used for detecting overflow errors
BlockIndex prevBlockIndex; // index of prev block (InvalidBlockIndex if first block)
BlockIndex nextBlockIndex; // index of next block (InvalidBlockIndex if last block)
MemoryPoolPage* pageObject; // pointer to memory pool page which owns this block
uint dataStartCanary; // used for detecting overflow errors
};

这个内存块头部信息主要包括内存块前置索引信息prevBlockIndex,内存块的后置索引信息nextBlockIndex,还有指向包含该头部信息的内存页pageObject。并且保持了这个内存块头部信息的大小刚好是16bytes。

通过程序让内存块的前后索引关联起来,并让第一个内存块的前置索引指向ListHeadIndex,最后一个内存块的后置索引指向ListTailIndex。代码如下:

for (blockIndex = 0; blockIndex <>numBlocks; blockIndex++)
{
BlockHeader* curBlockHeader = this->BlockIndexToPointer(blockIndex);
curBlockHeader->headerStartCanary = HeaderStartCanary;
curBlockHeader->dataStartCanary = DataStartCanary;
curBlockHeader->pageObject = 0;
if (0 == blockIndex)
{
curBlockHeader->prevBlockIndex = ListHeadIndex;
}
else
{
curBlockHeader->prevBlockIndex = blockIndex - 1;
}
if (blockIndex == (this->numBlocks - 1))
{
curBlockHeader->nextBlockIndex = ListTailIndex;
}
else
{
curBlockHeader->nextBlockIndex = blockIndex + 1;
}
}

第五步:初始化指向第一个未分配的内存块的索引firstFreeBlockIndex,和指向第一个已分配内存块的索引firstAllocBlockIndex。代码如下:

this->firstFreeBlockIndex = 0;
this->firstAllocBlockIndex = ListTailIndex;

到目前为止整个内存池的初始化工作就算是完成了,接下来我们将分析是如何分配内存和释放内存。

2009年2月7日星期六

Nebula3内存模块-memory.h

Nebula3的内存模块提供了一些操作内存的函数,要使用这些函数就要引入memory.h头文件。打开memory.h头文件:

#include "core/config.h"

#if (__WIN32__ || __XBOX360__)
#include "memory/win360/win360memory.h"
#elif __WII__
#include "memory/wii/wiimemory.h"
#else
#error "UNKNOWN PLATFORM"
#endif
#endif

对于win32平台我们只关心win360memory.h这个头文件就可以了。进一步打开win360memory.h文件,在文件的顶部有如下定义:

#if __WIN32__
#include "memory/win32/win32memory.h"
#elif __XBOX360__
#include "memory/xbox360/xbox360memory.h"
#endif

如果是win32平台,该文件还引入win32memory.h文件。打开win32memory.h后,我们看到几个针对win32平台操作内存的函数。这个几个方法是:

1.void Memory::Copy(const void* , void* , size_t ):调用操作系统的
void CopyMemory(PVOID ,const VOID *,SIZE_T),拷贝内存中的数据。注意void Memory::Copy(void*,void*,size_t)方法的前面两个参数刚好是和void CompyMemory(PVOID,VOID,SIZE_T)方法前面两个参数对调。

2.void Memory::CopyToGraphicsMemory(const void* , void* , size_t ):从系统内存中拷贝数据到显卡内存,这在有些平台上需要这样处理。在win32上就不需要了,该方法内部直接调用void Memory::Copy(void*,void*,size_t)。

3.void Memory::Clear(void* ptr, size_t numBytes):调用操作系统的void ZeroMemory(PVOID,SIZE_T);用0覆盖指定的内存。

4.void Memory::Fill(void* ptr, size_t numBytes, unsigned char value):调用操作系统的void FillMemory(PVOID ,SIZE_T,BYTE Fill);用特定的值覆盖指定的内存。

我们再次回到win360memeory.h文件中,看看这个文件中的几个重要的方法:

1.void* Memory::Alloc(HeapType heapType, size_t size):很明显这又是个内存分配的方法,但第一参数HeapType是什么呢?打开win360memoryconfig.h文件,我们看到 HeapType是一个枚举的定义,定义12种不同类型的堆。使用不同的堆类型是为了降低内存碎片和把相同的数据放在一起提高缓存的使用。

在 win360memoryconfig.h中还通过extern HANDLE volatile Heaps[NumHeapTypes];定义一个包含指向内存堆指针的数组。并使用void SetupHeaps()方法在程序启动的时候,对这12中类型的内存堆进行初始化。

从上面的分析中,我门知道Alloc(HeapType, size_t)函数就是在指定的内存堆上分配一个给定大小的内存空间。

2.void * Memory::Realloc(HeapType, void* , size_t):在指定的内存堆上重新分配内存空间。

3.void Memory::Free(HeapType, void* ):在指定的内存堆上释放已分配的内存空间。

4.void* __cdecl operator new(size_t):替换全局new操作,在ObjectHeap类型的堆上分配对象内存空间。

5.void* __cdecl operator new[](size_t):替换全局new[]操作,在ObjectArrayHeap类型的堆上分配内存空间。

6.void __cdecl operator delete(void* ):替代全局delete操作,在ObjectHeap类型的堆上删除内存空间。

7.void __cdecl operator delete[](void* ):替代全局delete[]操作,在在ObjectArrayHeap类型的堆上删除内存空间。

以上就是memory.h头文件提供给我们操作内存的主要方法。

2009年2月6日星期五

Mangalore框架执行顺序图

编译Nebula2

准备工作:
1. 安装Python
2. 安装Tcl/Tk
3. 安装wxPython
4. 安装DirectX9SDK
5. 安装TortoiseSVN
获取源码:
1. 用TortoiseSVN获取Nebula的源码https://svn.sourceforge.net/svnroot/nebuladevice
2. 将nebula2目录拷贝到任意一个盘符下,比如拷贝到D盘
3. 从http://sourceforge.net/projects/nebuladevice下载Nebula的依赖库包,安装到nebula2目录
环境变量设置:
1. 向 [编辑系统变量] 的 [变量值] 一栏中添加
C:\Python
D:\nebula2\bin\win32

编译Nebula:
1. 执行nebula2下的update.py
2. 选择VC7.1,选中所有项目,点击run

VC 编译环境设置:
1. 打开 VC 的 [工具] - [选项...]
设置,向其中添加这些路径:
可执行文件目录(bin):
C:\Python
D:\nebula2\bin\win32
包含文件目录(include):
C:\DXSDK9\Include
库文件目录(lib):
C:\DXSDK9\Lib
2. 编译生成的文件在D:\nebula2\bin\win32下

Fun With HTTP

现在Nebula3变得有点复杂了,很重要的是要明白运行的程序内部是怎么回事。在Nebula2,这主要是靠内置于游戏中的调试窗口(一个纹理浏览器,一个“watcher variable”浏览器,等等...)。但创建新的窗口是一件困难和烦人的工作(特别是因为排版代码)。对于Nebula3我想让这事情变得简单和强大:一个简单内置的HTTP服务器将为各种类型的调试信息提供HTML页面显示。这个想法不是新的,其他人已经这样做了,但对于Nebula2的IO和网络系统写一个HTTP服务器是一项很重的任务。

在Nebula3只需很少的代码就可以了,TcpServer类已经处理所有的连接,只要一对流读写器去解码和编码HTTP的请求和响应,用很短的时间就你可以写出一个HTTP服务器。

以下是它如何工作的一个大概:

1.创建和打开一个TcpServer对象。
2.为一系列TcpClientConnections每帧轮询TcpServer。
3.对于每个TcpClientConnection:
3.1添加一个HttpRequestReader到接收到的流。
3.2添加一个HttpResponseWriter到发送的流。
3.3决定根据请求要发送什么回去,并把结果填入到响应里面。
3.4调用TcpClientConnection.Send()

实际HTTP协议的编码/解码是发生在HttpRequestReader和HttpResponseWriter类中。

这是第一个从Nebula3应用程序到网页浏览器的信息:



现在缺少能输出带有实际内容的HTML页面的一些HtmlWriter,和一个能输出图像到web浏览器的HttpImageWriter。

原文: Fun With HTTP

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

2009年2月5日星期四

Nebula3内存模块-Heap类

要使用Heap类必须包含heap.h头文件,让我们看看heap.h中包含有哪些东西。

打开heap.h我们发现对于win32和xbox360平台有如下定义typedef Win360::Win360Heap Heap,说明起实际作用的是Win360Heap类。

接下来我们抛开有关内存的调试信息来分析一下Win360Heap类到底做了哪些事情。首先从Win360Heap类的构造函数开始,构造函数在一开始就通过HeapCreate(DWORD,SIZE_T,SIZE_T创建一个自动增长的堆,并且判断如果是在win32平台下默认开启low-fragmentatio-heap

Win360Heap类中有三个主要的方法是Win360Heap::Alloc(size_t size),Win360Heap::Realloc(void* ptr, size_t newSize),Win360Heap::Free(void* ptr)。我们逐一来看看这三个方法的功能。

1.Win360Heap::Alloc(size_t size)方法是调用操作系统HeapAlloc(HANDLE,DWORD,SIZE_T)函数在构造函数创建的堆上分配一块内存空间。

2.Win360Heap::Realloc(void* ptr, size_t newSize)方法是调用操作系统HeapReAlloc(HANDLE,DWORD,LPVOID,SIZE_T)函数在构造函数创建的堆上重新分配一个内存空间。

3.Win360Heap::Free(void* ptr)是调用操作系统HeapFree(HANDLE,DWORD,LPVOID)方法释放在构造函数创建的堆上分配的内存空间。

从上我们可以大概了解到Nebula3的Heap类允许我们创建一个动态增长的堆,并从该堆上分配所需要的内存。

2009年2月4日星期三

Python介绍和概述

Python现在是最流行的动态编程语言之一,紧接着是Perl,Tcl,PHP和最近的Ruby。虽然它经常被看做是“脚本语言”,但实际上它是和List或者Smalltalk类似写法的通用编程语言。今天,python被用在很多地方,从使用完就扔的脚本到提供24x7不间断服务的大型web服务。它还用在GUI和数据库编程,客户端和服务端web编程,程序测试。科学家还使用它为世界上最快的计算机编写应用程序并且是小孩子友好的初学语言。

在这个博客中,我将聚焦Python的历史。特别,Python是如何开发,在它的设计中主要的影响,造成的错误,经验学习和这个语言将来的趋势。

感谢:这个博客中很多好的语句都受惠于Dave Beazley(更多关于这个博客的由来,看我另外一个博客)。

鸟瞰Python

当人们第一次接触Python,他们经常是对Python的代码印象深刻,至少在表面上,Python的代码和传统的编程语言如C或者Pascal是很相似的。这不是偶然,Python的语法大量借鉴了C语言。例如,很多的Python关键子和C语言是一样的(if,else,while,for,等等...),Python标识符的命名规则和C是一样的。当然了,Python不是C,一个很大的区别是Python使用缩进代替C语言的中括号。例如,C语言像这样:

if (a < max =" b;" max =" a;" max =" b" max =" a" regex =" re.compile(r'href="" max="10):" data =" urllib.urlopen(url).read()" hits =" regex.findall(data)" href="http://python-history.blogspot.com/2009/01/introduction-and-overview.html">Introduction and Overview

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

2009年2月3日星期二

改变数学库

在编写图形和场景子系统时我意识到哪些数学代码是需要的。我原先的计划是编写一个看起来像HLSL的底层功能的学库并使用SSE提升效率。我开始编写并且很快清晰显示出这是一个非常正确的实现。那时仅仅作为一个SSE实现,还有SSE2..4,3DNow需要实现,并且在Xbox360和其它平台上在本质上完全不同。可以肯定,只要增加人力就可以解决这个问题。但用这种方法解决程序问题永远不是一个好办法。因此我开始寻找一个更有效的解决办法,很快从D3DX数学库中找到了答案。D3DX数学库的功能是非常全面的,尤其对于游戏,支持所有当前向量指令集,并且360的数学库基本上提供了相同的特性集。

这有两个缺点:

1.因为D3DX方法不是内联增加调用开销。
2.除了DirectX和Xbox360不能移植到其它平台。

但我可以容忍这一些因为它“现在”帮我减少了很多工作,移植一个数学库到其它平台并根据特定平台优化的工作包装方法和自己编写方法一样多。

其它一些考虑的方面:

1.使用c++数学代码,性能不能影响使用的方便。例如,一个operator+()操作因为需要构建一个临时的对象总是会损失掉一些性能。但在一般游戏代码中使用c++操作符重载比传统的方法更方便,更具有可读性。有一点要特别注意的是在内部循环使用底层代码是有它实际道理的。

2.在Nebula中只有很少的地方需要在CPU上执行大量的数学运算(在Nebula2中:粒子系统,动画代码,为skinned characters计算阴影。在Nebula3这些计算都交给GPU来完成,或者将被放弃掉)。一般来讲,CPU将不需要执行几何运算。

当我重写整个数学库的时候,我又做了一个我在所有时间都想做的改变,但这在Nebula2中是不可能的因为会破坏很多已经存在的代码:默认构造函数将不再初始化底层数学对象。我知道会有争论。但我想看看它如何在实践中证明。

另个一个基本改变是区分点和向量之间的不同。现在有一个Math::point和一个Math::vector类都是继承Math::float4类。一个点说明在3d空间中的一个位置,一个向量说明在3d空间中的一个方向和大小,以此推广到4维空间(如果是点的话W分量总是1.0,如果是向量的话W分量总0.0)。

(point + point) is 错误的
(point * scalar) is 错误的
point = point + vector
vector = point - point
vector = vector + vector
vector = vector - vector
vector = vector * scalar
etc...

并且,类的接口也变得清晰因为程序员可以立刻知道参数是期望传入一个点或者是一个向量。

因此新的数学库看起来像这样:

以下底层类直接调用D3DX方法:

* matrix44 (D3DXMatrix functions)
* float4 (D3DXVec4 functions)
* quaternion (D3DXQuaternion functions)
* plane (D3DXPlane functions)

所有其它通用类(像bbox,sphere,line等等)都是使用底层类提供的功能。这有一个新的scalar类型(其实就是float的预定义),可以帮助移植到其它平台。我一直都为数学库编写一套完整的测试类和性能测试类,但现在我非常地开心因为经过大约两天的实现我就可以减少一大块工作了。

原文: Math lib changes

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

2009年2月2日星期一

Nebula3资源系统

关于资源系统的更多信息。因为还在开发中,所以一些细节像类名可能会改变。一般讲,Nebula3资源系统足够的开放,并且相对于N2给开发人员对于资源的创建和管理更多的控制。

Nebula3资源有一下特性:

1.包装其它Nebula系统需要的各种数据。
2.能通过ResourceId共享。
3.在任何时候可以被加载和卸载。
4.可以被同步或者异步加载。

典型的图形资源如网格和纹理,然而资源系统不仅限于图形资源。

资源系统有两个操作层面(这可能被分为两个不同的命名空间,但目前都是放在Resources命名空间下):

底层提供了实际的资源对象,处理资源的共享,加载和保存。底层的资源类:

1.ResourceId
2.Resource
3.ResourceLoader
4.ResourceSaver
5.SharedResourceServer

资源系统的高层提供了资源管理,这意味着根据资源使用者的反馈动态地加载或者卸载资源。资源系统的高层类:

1.ResourceProxy(也可能选择类名:ManagedResource)
2.ResourceProxyServer(也可能选择类名:ResourceManager)
3.ResourceMapper

资源系统的这些类是如何一起工作的:

ResourceId是资源唯一标识符。资源标识符被用来共享,和定位磁盘资源数据的位置。资源标识符是一个具有原子性的字符串。使用一个唯一的32位标识来表式常量字符串,可以提高字符串拷贝和比较的效率,并且降低内存的使用,因为相同的字符串只会存储一次。为定位磁盘上的资源数据,资源标识符一般解析成一个有效的URI(一个资源标识符看起来像“texture:materials/granite.dds”这样,在运行的时被解析成像“file:///C:/Programme/[AppName]/export/textures/materials/granite.dds”这样)。

一个Resource对象才是真正的资源数据容器。特殊的资源类型像纹理和网格是Resource的子类并提供专门的类接口。Resource的子类经常是平台相关的(例如:D3D9Texture),但根据条件typedef一个平台无关的接口(例如:Texture)。不像在Nebula2中,资源对象不知道如何设置,加载和保存自己。替代的是,一套合适的ResourceLoader和ResourceSaver对象被增加到Resource对象中。Nebula应用程序很少需要输出数据,ResourceSaver更多的是为了完整性而存在。另一方面,ResourceLoaders是很重要的,因为它是设置Resource对象的唯一途径。ResourceLoaders完全控制了资源设置的过程。它们可能平台相关,也可能依赖一个与平台相关的Resource类。这相比于Nebula2给程序员更多控制资源设置过程。例如资源加载
类:StreamTextureLoader,StreamMeshLoader(从流中设置纹理和网格),MemoryVertexBufferLoader和MemoryIndexBufferLoader(从内存中的数据设置顶点缓存和索引缓存)。

Resource类为资源的同步加载和异步加载提供一个相同的接口。同步加载像下面这样:

1. res-> SetResourceId("tex:system/white.dds");
2. res-> SetLoader(StreamTextureLoader::Create());
3. res-> SetAsyncEnabled(false)
4. res-> Load()
5. if (res-> IsValid()) ... then resource loading was successful, otherwise the method
LoadFailed() will return true.

异步加载和上面非常相似:

1. res->SetResourceId("tex:system/white.dds");
2. res->SetLoader(StreamTextureLoader::Create());
3. res->SetAsyncEnabled(true);
4. res->Load();
5. the resource will now go into pending state...
6. as long as IsPending() returns true, repeatedly call Load()... of course a real application
would do something useful in the meantime
7. at some point in the future, after Load() is called, the state of the resource will either be
Valid (resource is ready for use), Failed (loading the resource has failed) or Cancelled (the
pending resource load has been cancelled)

一个应用程序或者甚至Nebula3的渲染代码通常不需要处理这一些,资源管理层将处理这些事情并把资源异步加载的细节隐藏在资源代理后面。

单例SharedResourceServer通过ResourceId共享资源。通过SharedResourceServer创建资源必须确定资源被正确地加载到内存中,不管它的客户端计数。如果一个资源的客户端计数减为0的时候,该资源将自动卸载掉。如果通过Nebula3标准的对象创建机制创建资源对象那么将无法进行资源共享。

ResourceProxy(或ManagedResource)是包装实际资源对象的代理对象。这个想法是基于资源使用情况的反馈,被包含的资源对象在资源管理器的控制下可能会发生改变。例如,一个TextureProxy可对象以在请求后台加载纹理的时候提供一个占位的纹理,假如所有的对象都在屏幕很小的地方使用这个资源那么可以提供一个低分比率的纹理,该纹理在X帧后没有被渲染就可以卸载掉。

单例ResourceProxyServer(或ResourceManager)是资源管理系统的前端。它是ResourceProxy类的工厂并且关联了和资源类型相关的ResourceMappers。

ResourceMapper是一个有趣的类。一个ResourceMapper和一种资源类型相关联(例如.纹理或网格)并通过应用程序添加到ResourceProxyServer中。一个ResourceMapper的职责是根据渲染代码使用资源的反馈加载或卸载资源。ResourcMapper的子类可以实现不同的资源管理策略,也可以通过派生特定的ResourceMapper和ResourceLoader的子类来创建一个完全定制,平台和应用相关的资源管理方案。显而易见,Nebula3提供了一些开箱即用的ResourMapper类。

资源使用情况的反馈是通过渲染代码写入ResourceProxy对象的并包含了其它一些信息如:资源在不久的将来还会需要吗?资源是否可见?并估计物体占用的屏幕空间大小。然而不同的反馈依赖ResourceProxy子类,在ResourceProxy类中没有相同的反馈方法。

根据资源使用的反馈,一个ResourceMapper需要实现如下操作:

1. Load: 在特定的层次细节(level-of-detail)上异步加载资源,在加载的时候为资源提供一个占位资符。
2. Unload: 完全卸载资源,释放有用的内存。
3. Upgrade:提高一个已经加载的资源层次细节。
4. Degrade:降低一个已经加载的资源层次细节。

原文: The Nebula3 Resource Subsystem

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

2009年2月1日星期日

Nebula3脚本系统

Nebula2的脚本系统实现了c++类的一个脚本接口,脚本命令直接映射到c++方法上。从技术角度上看这是一个不错的想法,但最后,对于脚本系统的主要使用者关卡设计师来说Nebula2的脚本系统太底层和不友好了:关卡设计师使用脚本编写游戏逻辑和行为。

关卡逻辑脚本通常在比c++类接口更高级的层面上编写。直接把脚本命令映射到c++方法上给脚本带来复杂度。bug可能比相同的c++代码还多,因为脚本语言通常是弱类型的并且无法在编译的时候检查错误,因此c++在编译时能检查出来的错误脚本却要在运行时才会发现。这是我们从Project Nomads项目中使用Nebula2脚本系统学习到的经验。

从中我们学到:让你的脚本处于合理的抽象层上。把c++接口映射到脚本语言上不是一个好的想法。

新的Nebula3脚本设计哲学是为关卡设计师在“合理的抽象层”上提供脚本支持。当然了“合理的抽象层”是很难定义的,因此必须在灵活性和易用性之间保持平衡。

除了太底层外,Nebula2脚本系统还有其它一些技术问题:

1.c++方法必须遵守脚本的约定(参数只允许使用简单的数据类型)
2.每个c++方法为了能脚本化需要额外脚本接口代码(在每个方法前增加几行代码)
3.只有从nRoot继承下来的类才可以脚本化
4.对象的持久化和脚本系统耦合在一起(因增加了依赖性使得重构变得更困难)。

现在Nebula3底层的脚本系统看起如何:


1.脚本系统的基础是Scripting::Command类。
2.Scripting::Command完全不依赖任何脚本语言,并由一个名称,和一些输入输出参数组成的。
3.一个新的脚本命令将继承Scripting::Command,并在OnExecute()方法中实现新的脚本命令的功能。
4.脚本命令在使用之前必须注册到ScriptServer。
5.在脚本子系统中ScriptServer是唯一一个和特定脚本语言相关的类,它用来注册新的脚本命令并转化参数。

这个设计比Nebula2简单并且最重要的是它没有和Nebula3的其它部分交织在一起。甚至只要简单改变#define就可以编译一个没有脚本支持的Nebula3。

当然了,使用c++写脚本命令还是像在Nebula2中那样繁琐。这就是NIDL的由来。NIDL的全称是“Nebula Interface Definition Language”。这个设计是为了减少重复劳动,尽量使用简单的XML来定义脚本命令然后把XML编译成实现Sceripting::Command子类的c++代码。

脚本命令有些重要的信息:

1.命令的名称
2.输入参数的类型和名称
3.输出参数的类型和名称
4.实际c++代码

不是很重要但很方便的信息:

1.为实时帮助系统提供关于该命令是做什么的说明和每个参数说明。
2.提供唯一FourCC编码。

大部分脚本命令转换成大约7行的XML-NIDL-code。用一个叫“nidlc”的NIDL编译工具把XML文件编译成c++代码。这个预先处理过程完全集成在VisualStudio中,因此程序员不会因为使用NIDL文件而引发更多的争吵。

为减少文件混乱,把一些相关的脚本命令整合在一起组成脚本库。一个脚本库对应一个NIDL-XML-file文件,并转化成一个c++头文件和c++源文件。脚本库会在程序启动的时候通过script server注册脚本命令,如果你的应用程序不需要或者不想通过脚本读取文件,那么你就不需要注册IO脚本库。这将减小可执行文件的大小,因为如果没有引用连接器将完全丢弃脚本库的c++代码。

最后,Nebula3放弃是使用TCL作为标准的脚本语言,并采用了具有很小运行时的LUA。LUA已经变成游戏脚本事实上的标准,因此比较容易找到已经熟练掌握LUA编程的关卡设计员。

原文: The Nebula3 Scripting System

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

Nebula3 Zip文件系统

当应有程序需要打开并读取许多很小的文件时,游戏应用程序经常使用压缩文件来避免混乱和提高效率。Nebula2使用的是私有的压缩格式(NPK),Nebula3将使用标准的Zip文件。这有几个优点:

1.不需要自己编写工具去创建压缩包,可以任选一种zip软件。
2.简单的文件加密支持。
3.占用更小的磁盘空间。
4.更高的读取效率,因为效率瓶颈一般是磁盘的带宽,而不是解压速度。

这种实现也有一些缺点:

1.不支持写(这不是个大问题,NPK也不支持写的方式,游戏资源通常都是只读的)。
2.不能随机读取,这个有点麻烦,可以用更高级的实现来解决。方法是把要读取的zip压缩包中的文件整个解压到内存中,这样就能随机读取在内存中的文件拷贝了。

一旦zip压缩包通过IO::Server::MountZipArchive()加载起来后,读取zip压缩包的内容将是完全透明的。IO::Server::CreateStream()方法将检查URI是否是zip压缩包中的一个文件,在需要的时候返回ZipFileStream替代FileStream。应用程序一般使用返回的Stream对象并不需要关心这是个“真”文件或者是在zip包中的压缩文件。

原文: Nebula3's Zip file system

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

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

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