Water ... that adapts itself to the flow, that breaks everything like a sword
RTTI: Changes for better
Get link
Facebook
X
Pinterest
Email
Other Apps
-
As some of you may know White Rabbit Engine use it's ownRun Time Type Information system. And well like with everything else sometimes come time when you need to upgrade stuff. Recently I had another occasion to do iteration over this system.
In this post I will try to give you a little bit insight how m RTTI look is engine, what changed and what are plans for future. But well let's start from begin.
Beginning
When I was designing own RTTI one of most important aspect was simplicity of use. When you adding RTTI to class it should be straight forward. Minimal amount of code you need to add and simple syntax. I think that in the end I achieve the goal but I won't say that it was like that right away. But well decide yourselves:
* Header
/*
==========================================================================
Some Rtti class
==========================================================================
*/
class CMyRttiClass : public CRttiObject
{
RTTI_CLASS(CMyRttiClass , CRttiObject);
public:
CMyRttiClass( void );
private:
wrStringID m_name;
CMeshResHandle m_meshResource;
void setResource( int32 a_idx );
};
* Source
// Includes
RTTI_DECLARATION_BEGIN(CMyRttiClass)
RTTI_ATTRIB(m_name)
.setAlias("Name")
.setEditorParams("[Unique]");
RTTI_ATTRIB(m_meshResource)
.setAlias("Mesh Resource")
.bindSetCallback(&CMyRttiClass ::setResource)
.setEditorParams("[Ext:'msh','mshxml']");
RTTI_DECLARATION_END
// Rest of code
...
Or use serialization of attributes that are added in declaration.
In above code all RTT_xyz are macros. They unwrap in meantime of compilation and sadly recently there was problem with parsing them by Visual Assist. It's intellisense stopped working in all my *.cpp with RTTI declarations which made me rally sad :(.
Occasion to changes for better
This was the moment for changes:] For some time I planed to switch part of RTTI on offline generated code. So I took my python project generator and added extra step. It parsing headers file in project where RTTI is enabled and generate from it "rtti_*.cpp" containing RTTI implementation .
* RttiGenerator.py
#!/usr/bin/python
import os
import re
#======================================================================
# This is RTTI code generator. It convert RTTI informations from class
# header to seperate generated *.cpp file.
#
# TEMPLATE FORMAT:
# Template file format is easy it's split on sections:
# [[SECTION_NAME]]
# Where you can select which part export by conditions:
# @if(conditionName)
# @else
# @endif
# And use variables (check : RttiGenerator.processTemplate(...))
#
# REMARKS:
# There is few advantages of this way of resolving RTTI code:
# * There is no issue with Visual Assist parsxing (yep there was issue
# when RTTI used macros)
# * You can step nicely into code with debugger.
# * Saving compilation time because generated code will end in separate
# object.
#======================================================================
class RttiGenerator:
def __init__(self, slnDir, rttiTemplatesDir, pch):
self.gen = None
self.slnDir = slnDir
self.pch = pch
self.templatePath = os.path.join( rttiTemplatesDir ,'template.rtti')
def processTemplate(template):
template = template.replace('{', '{{')
template = template.replace('}', '}}')
# Variables to use in template sctipt
template = template.replace('$(PCH)', '{pch}')
template = template.replace('$(CLASS)', '{classThis}')
template = template.replace('$(CLASS_BASE)', '{classBase}')
template = template.replace('$(FILE)', '{File}')
return template
def loadTemplate(self, path):
templateFile = open(path, 'r')
templateName = None
self.template = dict()
for line in templateFile:
line = RttiGenerator.processTemplate(line)
templateSectionRE = re.search('\[\[(\w+)\]\]', line)
if templateSectionRE != None:
templateName = templateSectionRE.group(1)
content = []
self.template[templateName] = content
elif templateName != None:
if line[0] == '@' or len(content) == 0:
content += [line, '']
else:
content[-1] += line
def getTemplate(self, section, conditions):
output = ''
stack = []
for line in self.template[section]:
conditionIf = re.match('@if\((.*)\)', line)
if conditionIf != None:
stack += [conditions[conditionIf.group(1)]]
continue
conditionElse = re.match('@else', line)
if conditionElse != None:
stack[-1] = not stack[-1]
continue
conditionEndif = re.match('@endif', line)
if conditionEndif != None:
stack = stack[0:len(stack)-1]
continue
if len(stack) == 0 or stack[-1] == True:
output += line
return output
def generate(self, path):
self.gen = None
pathRel = os.path.relpath(path, self.slnDir)
file = open(path, 'r')
conditions = dict()
conditions['Pch'] = self.pch != None
for line in file:
line = line.strip()
isInterface = False
searchRtti = re.match('RTTI\s*\(\s*(\w+)\s*\,\s*(\w+)\s*\)\s*[;]{0,1}', line)
if searchRtti == None:
searchRtti = re.match('RTTI_CLASS\s*\(\s*(\w+)\s*\,\s*(\w+)\s*\)\s*[;]{0,1}', line)
if searchRtti == None:
searchRtti = re.match('RTTI_INTERFACE\s*\(\s*(\w+)\s*\,\s*(\w+)\s*\)\s*[;]{0,1}', line)
isInterface = (searchRtti != None)
conditions['Interface'] = isInterface
if searchRtti != None:
classThis = searchRtti.group(1)
classBase = searchRtti.group(2)
if self.gen == None:
# Lazy loading of template file
self.loadTemplate(self.templatePath)
# Appending of formated header
fileHeader = self.getTemplate('HEADER', conditions)
self.gen = fileHeader.format(File=pathRel,
pch=self.pch,
classThis=classThis,
classBase=classBase)
# Appending of formated class header
classHeader = self.getTemplate('CLASS_HEADER', conditions)
self.gen += classHeader.format(File=pathRel,
pch=self.pch,
classThis=classThis,
classBase=classBase)
return self.gen
Above script is copy-paste from script I use. I think that comments in it tell most of the stuff about usage so I will skip duplicating it. So process works and allow me to do a lot more than before. There is only one issue: right now this step is done in meantime of project generation. So it not update when file change. Because of that I have already plan to include it in building process but well I will do it when I will find some time.
Even more nice changes
Other things I changed is style of casting. You probably don't remember it from above. I also had sometimes problems with remembering it's syntax :D So recently I decided to change it on something more intuitive :
This may not look like a big change but it's making stuff easier. Comfort of code writing is one of things we always need to take into account. If you think twice how to use something then it's mean that maybe something is wrong with it.
Plans for future
Right now except adding RTTI generation as building step I plan to move even more RTTI code from macros to generated *.cpp. There is also incoming revision of code :/ This maybe not be the funniest part but I want to check if all comments in it are still up to date and clarify few stuff there for behavior of system.
So like always start with problem description: I have some pool of command represented as enumerator. Each of command can have unique data that size can be different. I wanted to create system that allow me in easy way iterate over them and execute. After some time I created this implementation: template<ECommands::enum cmd> bool execCommandTemp(ECommands::ENUM a_cmd, void* a_data) { if (a_cmd == cmd) { SCommand<cmd>::execute((SCommand ::SData*)a_data); return false; } return execCommandTemp<(ECommands::ENUM)(cmd+1)>(a_cmd, a_data); } template<> bool execCommandTemp<ECommands::WRAP>(ECommands::ENUM a_cmd, void* a_data) { return true; } bool execCommand( ECommands::ENUM a_cmd, void* a_data ) { return execCommandTemp<(ECommands::ENUM)0>(a_cmd, a_data); } where SCommand look in example such a way: template <> st...
And as I said last time new preview is available: As you can see a lot has changed. Right now I mostly focus on gameplay so I do test of AI (some of them you can see on end of movie), changes in fight, items managing, fix some problem with physics and many other things. To say truth I don't remember them all :P So I'm going back to work and till next week.
If you try to find answer what good/bad code is, you won't find it in this post. I don't try to sell my beliefs and I'm sure you wouldn't like them anyway. To show you that, check this simple pieces of code: ////////////////////////////////////////////////////////////////////////// void CAnimationResource :: release ( void ) { SAFE_DELETE ( m_privateData ); } I use void when function don't use any arguments. I know this useless and do nothing but I just like look of it (Personal preference). Other example: ////////////////////////////////////////////////////////////////////////// CAnimationResource :: CAnimationResource ( const wrResourceID & a_id , IResourceManager * a_manager ) : CResource ( a_id ) , m_manager ( a_manager ) { } I using prefix a_ for arguments of functions and use this style of organizing initialization...
Comments
Post a Comment