search:   
Holy crap... programming?!!By Mithrandir      
Page:   1 2 »»

Tuesday, February 14, 2006
My awesome scripting system has a flaw, unfortunately.


Constructors...



So what happens if there are scripts that need constructors? It's easy enough to pass in an array of parameters, true; reflection handles that quite nicely.

The problem exists when scripts are reconstructed, due to there being a new version of a script. The easiest solution would be to keep track of the arguments passed into the original constructor, and pass those same arguments in again.

Of course, this isn't really such a grand solution, because of several things.

1) data that was valid when the script was originally constructed may not be important anymore. Say a script needed value X, and then during the course of executing, X changed. Now if you reconstruct the script as a new version, and pass in the old value of X again... well, you're kind of screwing things up!

2) There may be situations where the parameters to the constructor completely change.


So, I'm thinking, my scripting engine should just disallow constructors. There's really no easy way to handle this situation.


Now... the tricky stuff.


Using reflection to automatically copy details from one class to another is a hack, but it's the only hack I can think of. Why is it a hack? Well, because it ignores all the rules. For example, reflection can get private types, protected types, etc, and modify them on a whim, even if you really have no right to be touching that crap. Oh and the worst part is? I've successfully managed to overwrite a "readonly" variable. Ouch. How's that for code safety?


So why is this important?


Well, for example, there are times when this behaviour is desired, and times when it's not.

The best example of how ignoring the language safety features is when loading an object from disk. The loader is going to want to set the raw values of data, without worrying about pesky properties messing things up. For example:

public string Password
{
    get { return password; }
    set { password = Hash.SHA512( value ); }
}




The password of a player is never actually stored anywhere in the program. When you set the password, the account class smartly converts it to a 1-way hashed representation of the value.

If we try to set the password through the property, using the loaded value from disk (the hash), then the password will be completely messed up, because you're hashing the hash. This is not really a good thing.

So we need the ability to directly set the value, without worrying about protections and such. There are plenty of other uses.



Anyway, back to the topic; entity logic scripts in BM2.0 will be linked to entities. When you register a script with an entity, it will hook itself into the appropriate events (which aren't actually events, but dynamically-multicasted delegates stored in a dictionary based on event name), so the logic script is automagically notified whenever an event affects an entity.

Logic scripts cannot be transfered between entities, so their entity pointer should not be changable. Meaning that it should be a readonly value. Unfortunately, readonly values can only be set in a constructor, or hacked by using reflection, and if we can't use constructors...

oy, going in circles :)


So, I could make a stipulation in the scripting engine that says "You may use constructors, but make absolutely certain that the values never change!". I hate that. I don't want to voluntarily cripple my code for a specific instance! Plus, another problem is that I would need to require a parameterless constructor (for cases where the parameters aren't know beforehand, such as when loading a script from disk), which makes the users job harder by requiring 2 constructors, since C# doesn't allow constructor inheritance.

Or I could make entity pointers in the logic scripts not read-only... and face the potential of malicious (or incompetent) coders screwing up the game. No thanks.

So that leaves me with... using reflection.


The scripts won't have access to reflection, so I don't have to worry about scripts messing things up on purpose.

The problem is ultimately, constructors are a bad idea, because in two out of the three cases where they may be needed (1) loading from disc, 2) loading new version) we aren't actually "constructing" the object (technically we are), but merely loading a previously constructed state into a new object.


Sooooo, I think the best solution I can think of is to have a function, that acts kind of like a constructor, but is called on a script object once its data has been loaded. A function that says "Your data is set, now perform everything you need to do in order to put yourself into a valid operating state".


The three situations, then would be performed like thus:


1) Creating a brand new logic
1.1) Create logic (script engine)
1.2) stuff entity ID into it (game engine)
1.3) call the "Register" function (game engine)

2) Updating old logic with new version
2.1) Create new logic (script engine)
2.2) copy data over (script engine)
2.3) unregister old logic (script engine)
2.4) register new logic (script engine)*

3) Loading a logic from disk
3.1) Create new logic (script engine)
3.2) copy disk data into logic (game/file engine)
3.3) register logic (game engine)


* - I may change the order of 2.3 and 2.4, since registering the new logic may throw, and in that case, an unstable state may exist in the program.

Comments: 0 - Leave a Comment

Link



Monday, February 13, 2006
First, I've solved my XML dillemma, I think. This format seems to be the best mix of data minimalization mixed with a proper standard formatting:

<Entities>
    <Account id="1">
        <Attributes>
            <String name="name">MITHRANDURDUR</String>
            <String name="password">72B3F57E935DADD36ECA5207C34D91B05590325252C63DAEF2083051C62311B79089C300A1BFA3F80EB620F105A3730C70BC493C10AFCF46F94725F58DC2BA40</String>
            <DateTime name="lastlogin">2006-02-12 5:46:38 PM</DateTime>
            <Int32 name="allowedcharacters">2</Int32>
            <Boolean name="banned">True</Boolean>
            <Boolean name="canaddscripts">False</Boolean>
            <Boolean name="canadddata">True</Boolean>
            <Boolean name="canmodifyaccounts">False</Boolean>
            <String name="lol">LOL</String>
            <Double name="this is a really long ass data variable name lol">3.14158</Double>
        </Attributes>
    </Account>
    <Character id="2">
        <Attributes>
            <String name="name">Homer Jay Simpson</String>
            <Int64 name="room">0</Int64>
            <Int64 name="account">1</Int64>
            <Boolean name="dormant">True</Boolean>
            <Int64 name="template">20</Int64>
            <Boolean name="snarky">True</Boolean>
            <Int32 name="hp">80</Int32>
            <Int32 name="max hp">100</Int32>
            <Int32 name="mana">20</Int32>
            <Int32 name="max mana">20</Int32>
            <Boolean name="resting">True</Boolean>
        </Attributes>
    </Character>
</Entities>







The rules are:

The tag names will be type names, so we know how to decode everything under the tag, be it simple data, or an entire tree of sub-tags. This will eventually be integrated into my engine by way of a registry.

ie:

XmlDecoderRegistry.Register( "Account", new XmlDecoderDelegate( AccountDecoder ) );

or something like that.

The second rule is that attributes are to be used for figuring out how to create the data element. IE: entities will have ID attributes, because they are required by the entity constructors. Base data elements will have the name of the element in the attributes, because we need to know where to put that element when it's read.

I like it, it's smaller than the previous version, and it seems to work well.




Secondly

Another big development over the weekend is the scripting engine. It's freaking cool.

CodeLoader loader = new CodeLoader();
loader.LoadCode( "hello.cs" );
MessageScriptAdapter h = loader.CreateScript<MessageScriptAdapter>( "BetterMUDScript.Hello" );
MessageScriptAdapter g = loader.CreateScript<MessageScriptAdapter>( "BetterMUDScript.GoodBye" );

string message;
message = h.Message();  // "Hello!"
message = g.Message();  // "Good Bye!"

loader.LoadCode( "hello2.cs" );
message = h.Message();  // "Hello, dudes!"





This simple example demonstrates the ease of the engine. Firstly, all you need to do is send it a file to load, and it will load the file for you. At the moment, there is no mechanism for reporting errors or other fancy things.

The engine will load the .cs file, and put it in its very own assembly (in memory). It goes through every exported type in the script, and takes note of every class that inherits from "Script", and puts them into a registry. The code engine will only allow you to instantiate classes that inherit from "Script", though it will still compile classes that aren't scripts.

The scripting engine works using an adapter pattern for each script. When you request a script from the engine, it instantiates a new script, and packages that script up into an adapter object (MessageAdapter in this example). For all intents and purposes, you will interface with that adapter object.

Now here's the cool part.

See the last two lines? It loads a file "hello2.cs", which contains a newly updated version of the BetterMUDScript.Hello class, which returns a different string.

When the code engine loads that file, it notices that the class "Hello" has been loaded before, so it looks to see if there were any adapters registered for the old version. If so, it goes through every adapter and updates its script object, so the next time you use the adapter, it will use the newly updated version.

Another cool thing is that when a new script is loaded, the engine will go through the old script and copy over any data that may exist.

This was an issue that was never resolved in BetterMUD 1.0. I use reflection to do this, which can be slow, but I'm not that concerned, because loading new versions of scripts isn't going to be something that is going to be happening often.


The coolest part is that it even tries to coerce data into a new type if the type changes.

For example, if the old version of Hello has an int named "x", and the new version has a float named "x", it will copy the int value of x into the float x. It works the other way around too, but you'll lose precision.

At the moment, the engine only copies datatypes that are considered "basic types", which include all integral types, chars, floats, doubles, decimals, strings, datetimes, and timespans. For some odd reason, .NET doesn't think a TimeSpan is a basic type, so I had to go through some trouble to create a flexible converter class, which essentially does everything that System.Convert does, except it works with TimeSpans too.

On the plus side, Tangential.Utilities.Converter is extensible. You can add custom converters to it as you see fit:

Converter.RegisterConverter( typeof( string ), typeof( TimeSpan ), new Converter.ConvertDelegate( StringToTimeSpan );
Converter.RegisterConverter( typeof( DateTime ), typeof( TimeSpan ), new Converter.ConvertDelegate( DateTimeToTimeSpan ) );
Converter.RegisterConverter( typeof( TimeSpan ), typeof( TimeSpan ), new ConverterConvertDelegate( Identity ) );

<snip>

TimeSpan ts;

ts = (TimeSpan)Converter.Convert( "00:10:15", typeof( TimeSpan ) );
ts = (TimeSpan)Converter.Convert( "01:10:00", "TimeSpan" );
ts = Converter.ConvertTo<TimeSpan>( "20:20:06" );






The system automatically handles ways to convert types based on
1) dynamic typing
2) dynamic typing based on a string name (grin)
3) static typing using generics at compile-time

So it's pretty cool.

And it works.




The only caveat with the system that I have is that coding adapters for each script type could be a pain in the butt. I can't think of any simple way to generate an adapter class in C# that automatically wraps around another class with the same interface. I could use reflection, I suppose, but I think that's a little overkill.

Comments: 2 - Leave a Comment

Link



Friday, February 10, 2006
When I first started on my current project, oh, say about 3 years ago, I said to myself "Well, I would like an extensible data storage format, since my game entities will all be very highly dynamic. I think XML is probably the way to go, but at the moment, I don't have time to learn XML, write about it (this was for a book), and use it efficiently, so I'll roll my own limited format!".


Fast forward 3 years, I'm now taking the project out of mothballs, and decided to make it everything that it deserved to be.


One of the things I always wanted to do was use XML for the data storage. When I decided to use .NET, I had an epiphany... .NET has XML classes built in; this should be a snap!

20 failed snaps later, I'm thinking of throwing out the whole idea.


XML seems like a poor substitute for what I need. Even more, I feel like I tried using a torque wrench to pound a nail.

Does XML offer me what I need? Of course.
Do I have to fight with it and use it in strange, twisted, unnatural ways? Yeah.
Is XML the solution I want? I don't know.


For example; For the bloody life of me, I can't figure out what the hell the point of XML attributes are, except for uneccessarily ambiguating the language (is ambiguating a word?).

I see examples all over the internet like this:

<ingredient amount="3" unit="cups">Flour</ingredient>




Now what I don't get is how this is legal, and yet this, is also perfectly legal:

<ingredient>
    <amount>3</amount>
    <unit>cups</unit>
    <content>Flour</content>
</ingredient>




Or any of 100 other permutations. It seems to me that there are 500,000 different ways to represent a complex object in XML... so which way is the right way?

At the moment, I'm using XML like this:

<Room>
	<long name="id" value="46575"/>
	<string name="name" value="Big Room"/>
	<long name="region" value="047"/>
	<float name="databank var 1" value="3.14159"/>
	<Script>
		<string name="name" value="Prevent Users From Leaving Unless They Have Item"/>
		<long name="item" value="42566"/>
	</Script>
</Room>




Right. That's retarded. How about this?

<Room>
	<data type="long" name="id">46575"</data>
	<data type="string" name="name">Big Room</data>
	<data type="long" name="region">047</data>
	<data type="float" name="databank var 1">3.14159</data>
	<Script>
		<data type="string" name="name">Prevent Users From Leaving Unless They Have Item</data>
		<data type="long" name="item">42566</data>
	</Script>
</Room>




This seems to be more along the lines of what XML designers like to use... but it's bigger, and I don't like the arbitrariness of treating types, names, and content as attributes and content. Argh.

Quote:

* "XML is a giant step in no direction at all" -- Erik Naggum
* "Some people, when confronted with a problem, think 'I know, I’ll use XML.' Now they have two problems." -- dirtsimple.org
* "XML is like violence - if it isn't working properly, you aren't using enough of it." -- Anon


*sigh*

Comments: 4 - Leave a Comment

Link



Wednesday, February 8, 2006
Why does the second side of Abbey Road sound like it was written by someone with ADHD?


I'm not complaining; I rather like it... it's just... it seems uncharacteristic and a little weird.

Comments: 3 - Leave a Comment

Link



Monday, February 6, 2006
I'M AS MAD AS HELL, AND I'M NOT GOING TO TAKE THIS ANYMORE!

Comments: 8 - Leave a Comment

Link



Thursday, February 2, 2006
I decided to play around with some different stream buffer concepts in .net, to see what kind of storage mechanism would be best for my high performance networking engine.


In the process, I came up with two paradigms:

NaiveBuffer
- Data always starts at index 0
- when the buffer is full, it autoresizes to twice its previous capacity
- removing data from the beginning of the buffer moves everything at the end downwards

CircularBuffer
- Data can start at any arbitrary index
- Resizes just like NaiveBuffer
- Oftentimes, data will cross the end of the array and overlap into the start of the array
- uses arraysegments to access the two parts of the array


So we have...

                        Naive      Circular
Insertion of x items    O(x)       O(x)
Deletion of x items     O(n)       O(1)



where n is the count of the items in the buffer. Note that both insertion algorithms get bumped up to O(n) whenever they need to resize the array.


So my benchmarks:


Firstly, a comparison where every iteration adds X bytes, then removes Y bytes, where Y is less than X.

The winner should be obvious, but I never imagined by how much it would win.

CircularBuffer<byte> cbuffer = new CircularBuffer<byte>();
NaiveBuffer<byte> nbuffer = new NaiveBuffer<byte>();
byte[] b = new byte[1024];
System.Random r = new System.Random();
int seed = DateTime.Now.Millisecond;
int add;
int remove;

DateTime start;
DateTime end;

r = new System.Random( seed );
start = DateTime.Now;
for( int x = 0; x < 100000; x++ )
{
    add = r.Next( b.Length - 1 );
    remove = r.Next( add );
    cbuffer.Add( b, add );
    cbuffer.Remove( remove );
}
end = DateTime.Now;
TimeSpan ctime = end.Subtract( start );


r = new System.Random( seed );
start = DateTime.Now;
for( int x = 0; x < 100000; x++ )
{
    add = r.Next( b.Length - 1 );
    remove = r.Next( add );
    nbuffer.Add( b, add );
    nbuffer.Remove( remove );
}
end = DateTime.Now;
TimeSpan ntime = end.Subtract( start );





results:

circular buffer: 0.20 seconds
naive buffer: 17 minutes, 23.83 seconds

ouch.


Now, unfortunately for Mr. Circular buffer, real-life network traffic probably isn't going to work like that. A more realistic test is if we queue up multiple messages, and then remove them all when they are sent (rarely will they not all be sent):

CircularBuffer<byte> cbuffer = new CircularBuffer<byte>();
NaiveBuffer<byte> nbuffer = new NaiveBuffer<byte>();
byte[] b = new byte[1024];
System.Random r = new System.Random();
int seed = DateTime.Now.Millisecond;
DateTime start;
DateTime end;
int reps;


r = new System.Random( seed );
start = DateTime.Now;
for( int x = 0; x < 5000000; x++ )
{
    reps = r.Next( 1, 16 );
    for( int y = 0; y < reps; y++ )
    {
        cbuffer.Add( b, r.Next( 1, b.Length - 1 ) );
    }
    cbuffer.Remove( cbuffer.Count );
}
end = DateTime.Now;
TimeSpan ctime = end.Subtract( start );


r = new System.Random( seed );
start = DateTime.Now;
for( int x = 0; x < 5000000; x++ )
{
    reps = r.Next( 1, 16 );
    for( int y = 0; y < reps; y++ )
    {
        nbuffer.Add( b, r.Next( 1, b.Length - 1 ) );
    }
    nbuffer.Remove( nbuffer.Count );
}
end = DateTime.Now;
TimeSpan ntime = end.Subtract( start );





I increased the iterations to 5,000,000 from 100,000 to give more meaningful times.

circular: 20.48 seconds
naive: 14.26 seconds


Sorry circular, you're incredibly awesome in the worst case, but your overhead in typical circumstances says that NaiveBuffer is faster.






Regardless, I still think I'm going to use the circular buffer anyways. The overhead isn't really that great, and 5,000,000 iterations will probably take the server over 10 minutes to reach in the real world anyways, so I don't think it's a huge deal.

Comments: 0 - Leave a Comment

Link



Tuesday, January 31, 2006
Every 3-4 years or so, the Lego company puts out a new "Supercar" technic model. When I was young, I could never afford these things, but with my newfound wealth, I decided it was about time to buy one. Lego's current top-of-the-line car is a 1:10 scale model of the Enzo Ferrari, a car named after the founder of Ferrari.

It is the fastest street-legal car ever made, having a 6 litre 12-cylinder engine (drool). Only 400 of the cars were made in total, one of them selling for over 1.2 million dollars.

Obviously, the real thing being outside of my financial zone, I had to settle with the lego version, for a cool $100.


First, the good:

The model comes with over 1300 pieces, putting the price/part ratio at a cool 7.3 cents per piece. This is somewhat below the average 10 cents per piece that you usually find with technic sets, so that's a bonus.

It comes with 7 160 degree #3 angled axle joiners, which are pretty rare in the lego world, so that's a plus.

It comes with 7 red hard axles, which is extremely rare, since most hard axles come in black and grey only.

It has a ton of red and black technic beams, so if you like to build red creations, this is the set for you.

It comes with 4 really neat hubcaps, something I've never seen in a lego set before. They really go a long way towards making the standard lego racing wheels look better.

And finally the best part: it comes with two CV joints, extremely rare and useful pieces, which can be used to make a powered wheel turn and have a suspension.

As for the model itself, the base is extremely well designed and I might use it (with some modifications) for other projects I create in the future.

The so-so:

It only comes with 8 gears, and 4 of those are for the rear differential. Don't get me wrong, rear differentials are cool, but almost every medium-to-large technic vehicle has them (I have 9 differential gears), so they're really nothing special anymore. Most supercars in the past have had 3 differentials to give a full 4-wheel drive mechanism, but sadly, this feature was lacking in this car.

The gull-wing doors are a little disappointing, in that they're pretty flimsy, and closing them causes them to hit a panel fairing. I really wish they would have gone with a sturdier design.

Also, it comes with 2 damped shock absorbers (for the doors), which were really cool when they first came out a few years ago, but now they tend to be overused and have lost some of their luster.

The bad:

PANEL FAIRINGS. Ugh, I've always hated these pieces, since they don't really seem to fit anything well. Lego tried making a one-style-fits-all piece to fill gaps in models, and unfortunately, we've got these stupid fairing pieces. Don't get me wrong, sometimes they work great (like with the 1:10 Ferrari F1 racer), but in this case, they don't work at all. The fairings are too small to cover up all the big holes in this huge car, so we end up with something that looks like about half of the outside of the car was torn off by a tornado.

The CV joints were awesome... but horribly unused in the model itself. There's no suspension or front-wheel drive, so they simply sit in the rear wheels, not doing what they were designed to do! Why?!!!

There's no transmission. This is the first technic supercar to my knowledge that has not had a gear transmission of any kind, which is disappointing, because the transmission ring pieces are really rare and I was hoping this would at least come with one. I only have one transmission ring, and it's currently inside my huge crane that I don't feel like disassembling (since it's so damned cool).

There are stickers that are meant to be placed on the car... spanning multiple pieces. Not a chance in hell, Lego. The whole point of Lego pieces is that you can disassemble them and build something new; I'm not going to permanently join together 3-4 pieces by putting a sticker on it!!


The verdict:

I'm more pleased than displeased with this purchase, but that's only because I can tear it apart and build it better on my own (ie: the whole point of Lego). I realise that Lego has a contract deal with Ferarri, and they were bound to make this set as close to the real thing as possible, but unfortunately that ended up making it somewhat sub-par.

As a parts pack, this is a good investment, however, and I would reccomend it to anyone who wants to build their own red lego sports car.

If you just want to build the ferarri enzo, then wait until it goes on sale for 50% off.

Comments: 4 - Leave a Comment

Link



Monday, January 30, 2006
http://www.gamedev.net/community/forums/topic.asp?topic_id=372576

Comments: 0 - Leave a Comment

Link



Monday, January 23, 2006
My job just went from super-awesome to super-suck. They decided to roll out a new "secure" system on all the workstations, and now we can't even install Microsoft Intellipoint to configure our mice.

It's "insecure".


They even disabled the USB ports on everyone's computers, so now I can't even listen to music.

Fuckers.



The worst part is, if anyone wanted to steal anything on our network anyways, all they would have to do is plug in an ethernet drive to the network and transfer files over in 5 seconds flat. So the new "security" is pointless.

Comments: 8 - Leave a Comment

Link



Wednesday, January 18, 2006
I have never been happy with the design of BetterMUD. I feel like I failed miserably when I created the event notification and permissions system... it just wasn't pretty.

I've been thinking about the design for 2.5 years though, and nothing has ever caught my eye as a better solution... until now.


It hit me the other day. It's absolutely brilliant. It took me 2.5 years but I finally figured out a system that actually makes sense!!!


Maybe now I'll be inspired to finish BetterMUD 2.0.

...

:)

Comments: 4 - Leave a Comment

Link



Tuesday, January 17, 2006
1) Jack Bauer cannot die. Ever.
2) Jack Bauer ALWAYS saves the day.
3) Jack Bauer even saves the day multiple times per day.
4) The only reason you're even alive is because Jack Bauer saved the day.

Comments: 1 - Leave a Comment

Link



Friday, January 13, 2006
LOL PRESIDENT JACK BAUER MEETS SAM GAMGEE MEETS ROBOCOP


POE TAY TOES.



TELL ME WHERE THE BOMB IS!!!



Come quietly or there will be... trouble.

Comments: 4 - Leave a Comment

Link



Thursday, January 12, 2006
It's 56 degrees outside.


?!!!?!

Comments: 6 - Leave a Comment

Link



Tuesday, January 10, 2006
I played around with Dungeon Siege 2 over the weekend.

It's pretty, but nothing that hasn't been done 500,000 times over already.


And seriously, what's the deal with medieval RPG's and empty barrel smashing? Why does every single game like this devolve into "smash 50 barrels in an area and find 1 gold piece in about 10 of them, and nothing in the others"?

Why are there so many barrels lying around with nothing in them?

Why are people putting one or two gold pieces in random barrels anyways?

Why can I carry 8 suits of armour on me, as well as several swords and an axe and 2 shields?

Why does everyone have a stupid name like "Valdis" or "Ventar" or "Aelurin" that I can't possibly remember, and therefore makes the story impossibly difficult to follow?

How come when I kill a guy who was just attacking me with a huge sword, there is no huge sword on the ground when he dies?

Why do most fantasy mythos' end up having "objects of power", where the main characters get most of their abilities from objects, rather than innate skills?

Doesn't that kind of send a materialist message, that you can only get places if you have certain things?

For that matter, why do they all have elves and orcs and goblins and silly stereotypical tribal midgets that talk like Yoda on crack?

Where is that large automobile?

Is this not my beautiful house?!

Is this not my beautiful wife?!!

Well... how did I get here?

Comments: 6 - Leave a Comment

Link



Monday, January 9, 2006
When I was suspended, I had a ton of things I wanted to come in here and write about.

Now that I'm back, I can't remember a single one of them.

Comments: 3 - Leave a Comment

Link

Page:   1 2 »»

All times are ET (US)

 
S
M
T
W
T
F
S
1
3
4
5
7
9
11
12
15
16
17
18
19
20
21
22
23
24
25
26
27
28

OPTIONS
Track this Journal

 RSS 

ARCHIVES
February, 2006
January, 2006
December, 2005
November, 2005
October, 2005
September, 2005
August, 2005
July, 2005
June, 2005
May, 2005
April, 2005
March, 2005
February, 2005
January, 2005
December, 2004
November, 2004
October, 2004
September, 2004