Page top

2011/03/20 (Villainmad): GameEntity.Type = Shenanigans

Created: / Modified:

Always use secure-HTTP / Secure HTTP / Permanent Link

So, the transfer to C# is coming along nicely; as of today, I am just about at the point I was when I dropped Lua. Here it is, if you want to see what I was up to when I posted this; you'll need XNA Redist 4.0, DirectX 9, and ... *squints at GetAssembly info* ... Windows 7, Vista SP1 or later, or XP SP3. (I ought to make an installer which will handle those requirements for me.) Feel free to tinker with the scripts there to see what happens. It also has a half-completed "template" file for the game-system, I probably don't need to include that yet, though, until I implement mod-functionality (which is WAY off down there in the future).

Sometimes, the solution to a problem is just a matter of dropping one of your assumptions and looking at it differently. Case in point: my difficulty with how to define a game-entity's type.

Here's what I was doing in Lua: Entities were defined by Lua-tables which, roughly (very roughly) translated into C# terms, functioned as static classes. It would passively, y'know, just hold the data there, and then when you wanted to execute functions like Update() and Draw(), the system passes all the relevant data to said functions, rather than attaching them to eacn and every single object. (In other words, say you have SomeRandomBullet using the data-table Ball: instead of calling SomeRandomBullet.Update(), it calls Ball.Update(data for SomeRandomBullet). This is so we only have one object-instance for every type of entity, instead of one object-instance for every single one of the 500 bullets onscreen.)

I was trying to figure out how to use a pretty much identical system within C# (I should've learned from previous experiences that there's no way I'm going to be able to set things up in C# identically to how I did it in Lua), except with literal static classes. The problem with this approach was, "How do you reference a static class as a variable?" After much hemming and hawwing, I really had no idea how to proceed, because I didn't know you could use typeof(classname) to use type-variables or the existence of Type.IsSubclassOf(ParentType). Even then, though, needing to use typeof() like that for every instance seems needlessly cumbersome, even though I don't mind using delegate(){ ... } all the time.1 So instead, I switched to instances of a dynamic class. This ... is also kinda cumbersome, I think, especially since it's that much harder to get at one type from within another type (cuz of encapsulation, doncha know). I dunno, really, I can see it going either way. I might switch back~

Another problem I had was that I had unexpected difficulty with the Ball type in the test-stage, which was behaving differently from the way I thought it should be; I spent a good portion of an hour trying to figure out what was wrong. The problem turned out to be that I'd been spending all that time looking at the Blah type2 instead.

So yeah, we are officially Coming Along Nicely. Tomorrow, I will go to the post office; waiting for me there will be an Edirol SD-90, the type of synthesizer used by ZUN. The first tune I make with it will probably be Masha's theme, but it will be ... highly useful, in the time to come. In any event, if part 1 of this project is the creation of the base-system on which the game will run, I can see the end from where I am.

1Largely because there literally is no other way.

2I am terrible with names of "example" variables.

49 Comments (auto-closed) (rss feed)

Page 1 2 / Previous / Next / All

Kimiko Muffin

I did not know you could do class ClassName <Foo> where Foo : Bar, and it is an awesome thing.

A chicken passeth by

Yes, you just discovered Templates. Which I'm not too experienced at, so I'll leave it to the other 2. >_>"

Silver

/ Modified by Kimiko Muffin:

Beginner-friendly articles on Generics.
Constraints on the type-parameter a.k.a. the where clause.

General overview article on C# Generics, basically an compressed infodump.

[moved to the correct article by Muffin]

Kimiko Muffin

The first thing I thought upon seeing the where T : Employee example in the second link was "Hmm ... I wonder what might be a subset of emploLIKE A BAWSS"

(I already knew about <type T> itself, though ...)

Kimiko Muffin

Hmm. I've discovered that I'm not entirely sure how to create a new instance of type-parameter T if T doesn't have a constructor that takes zero arguments, without using typeof(). I can't find any information on how to do so in the helpfiles, either. I can do it with typeof(), but you guys have prejudiced me against using reflection at all, even just at the beginning of an object's life. I know you're not going to be creating more than a few objects within an individual frame, but ...

(For entities, I also don't want to just handle spawning entities by just constructing it and letting the base GameEntity constructor add it to the entity-list, which would mean doing away with an AddEntity() method which returns an ObjID, because 1. I want the entity-list to remain private so you can't just mess around with it, 2. I don't want to keep track of the entities by reference to GameEntity because if there's any number of references floating around, it gets in the way of garbage-collection, and 3. it feels messy to just add an ObjID to the GameEntity's non-static variables and keep track of it with ObjID oneBullet = new SomeBulletType(<params>).ObjID, especially in light of the other two.)

A chicken passeth by

Why don't you overload the constructor (redeclare the default one - a parametrized one declared means the compiler won't generate one for you)?

From what I recall of my little use of templates you are allowed to use Constructors with member initialization lists or default parameters, so if a class at T has a constructor that looks like

(): var1(value), var2(value) {} or
(var1 = value, var2 = value) {}

the template knows what to do. You'll probably have to decide on some default values for these things (which is good programming practice anyways), then change the values later if you need to.

Kimiko Muffin

Hmm. Yeah, that makes sense, when I think about it. For GameEntities, it'd be easy enough to set the default values to zeroes (or possibly -1 in the case of CreationFrame). For GameController (which frankly kinda need another name) and Stage, though, it's a bit trickier, since they are both based on the DrawableGameComponent class, which requires a Microsoft.Xna.Framework.Game in its constructor. Fortunately, I can cheat by using static variables on ... well, any of the classes involved, really.

Silver

If this is a one-time only thing (like say, in a static constructor), I'd say reflection is perfectly alright (if all you're worried about is the performance of the thing; it is only ever done once anyway). IIRC there's no other easy way (at least in C# 2.0. My .NET knowledge is rusty).

This Microsoft Connect post suggests an alternative workaround:

When constructing your generic type, pass in a delegate which is capable of creating an instance of the type argument.

which isn't exactly what you're looking for, I would think?

C# generics are rather half-assed.

Kimiko Muffin

*passes by on the way to bed* I'm not sure I'm awake enough to actually understand the workaround.

(T)Activator.CreateInstance(typeof(T), new object[] { args }); was what I was using before, at least for the Stage and the GameController, which is probably okay since you're only creating each one one at a time and then only rarely. GameEntities ... who the zark knows, man.

Toawa

Regarding garbage collection, I'd think as long as those entities are in use, you wouldn't want the referenced GameEntity to be garbage collected. But if you're really worried about that, there's always WeakReference.

Reflection's fine for stuff that doesn't have to happen very often. If it happens once at startup and it simplifies things, do it. It's for the stuff you have to do hundreds of times per frame that you get in trouble with.

Silver, that now makes me wish that C# had some analogue to Obj-C's protocols and their class method specifications (and I never thought I'd say that...) Though it's lower on my wish list than Java-style enums-as-classes-with-methods-and-such and C/C++ const types.

Also, the delegate to the constructor is a valid pattern, as is Factory (which is the same thing with an extra level of indirection but the ability to more easily modify creation parameters, although the same effect can be had with lambda expressions...)

Toawa

Activator is basically reflection, but potentially even slower (since with reflection you have some chance of memoization). I think it's generally used for COM interop anyway.

Kimiko Muffin

I was referring to GC for once it wasn't in use anymore. Let's say you're keeping track of a GameEntity by ObjID. If it gets deleted, and that spot is subsequently replaced by another GameEntity, that means there's no more references to it, and it gets GC'd. But if you're keeping track of it by reference, that means there could still be any number of other references to it.

And yeah, as for the dangers of reflection, consider me fully briefed on the matter. ;) I'm not quite sure how I'll handle all the values for the GameEntities, since I want CreationFrame to be read-only, but ... hmm. More stuff to figure out.

... and even now that I'm more awake, I'm still not sure of what the workaround in the Microsoft Connect post is supposed to be.

Kimiko Muffin

/ Modified by Kimiko Muffin:

I think it would be more useful to have an ObjID than just an int-CreationFrame, just so it can keep track of itself. Which makes it even harder to work with only parameterless-constructors (since otherwise, I could just have the constructor do "get the stage's current frame").

EDIT: I think I have an idea of how to use delegates now, though ...

Kimiko Muffin

/ Modified by Kimiko Muffin:

Okay ... Pos, Vel, and Rot can be set at any time, but here's the thing I'm now banging my head against, now that I have ObjID GameEntity.ID instead of CreationFrame:

1. Class Stage contains List<GameEntity> EntityList, List<int> IndicesUsed, and List<int> IndicesUnused, as a variation on this method. They are all private because nothing good can possibly come of changing them outside of the specific methods for adding and removing GameEntitys.
2. ObjID GameEntity.ID must be generated, using information taken from EntList, IndicesUsed, and IndicesUnused. Properly adding and removing a GameEntity also requires directly manipulating all three lists. Because of the amount of everything likely to happen within a game, GameEntity.ID cannot be known in advance.
3. Because no good can possibly come of changing it, GameEntity.ID is read-only. As far as I know, it must therefore be set in the constructor.
4. If my understanding of the use of Delegates in this context is correct, it must be constructed at a point in which the type is already known. Or, to put it another way, Delegates aren't a solution that works, given the above.

I could use Property-shenanigans so that if you try to set it outside of designated areas, it will throw an error at run-time, but for reasons which I hope are obvious I'd greatly prefer if it threw an error at compile-time instead.

There's also the idea of having some sort of "next available ObjID," to be set in the line immediately before the GameEntity is constructed; you could get it publicly (i.e. from within the constructor), and then have the add-object method actually manipulate the lists. Something feels ... off about that, though, for some reason ...

Toawa

If you're keeping track of GameEntity by some sort of ID, what happens if your ObjectEntity needs to get ahold of it? You'll have to do some sort of lookup, which takes more time than if you had the reference there to begin with. And, while its true that the GameEntity won't get GCed if something has a reference to it, as long as it has a reference, you probably don't want it to be GCed. And if it's a bunch of short lived entities holding references, remember that they'll get GCed too, since GC sweeps go (approximately) by reverse age (newest objects get checked for GC first).

All of this falls under the category of "optimizing too soon". Get it working, then get it working fast, then worry about memory. Remember, computers have a lot of it nowadays. (I figure if I keep telling people that, eventually I'll start believing it myself and take my own advice ;)

Kimiko Muffin

I'm not sure what you mean by the "ObjectEntity." Did you mean the Stage object? Sequences? (Have you looked at my source code, which I linked in an earlier comment?)

The other thing about using a direct-reference instead of an ID to keep track of it is that I'd have to add some extra functionality for determining if it was deleted, or else I'd still have to do it by looking up "is this entity's index listed in UsedIndices? Yes? Okay, is this object the same as the one at that index?" This isn't an objection, I'm starting to come around to that point of view, I'm just thinking out loud (y'know, right before bedtime).

The other major obstacle I can think of is: how to change the GameEntity-type. It would have been simple enough when "type" was a separate variable on the GameEntity struct, but you can't change the class of an object, nor can you replace all references to it. Lookup-via-ID is the only way I can think of that you can reliably keep track of an entity after doing a "SetType()."

Toawa

I think I must have a bad model in my head, then... You must be doing it in a different way than I thought. That being said, the point still stands; you should generally prefer direct references over indirect ones when the former is practical and there aren't any functionality limitations that arise from it (IE, if you might be swapping out references for 10,000 objects from one instance to another, it'd be more practical to have an indirect reference by ID than a direct one.) GC considerations should not come into it until you've gotten it working and profiled, unless you know you'll be working with huge blocks of memory (hundreds of megs).

Kimiko Muffin

Lemme explain, then. The entities are stored in the variable EntList, which is an array of List<GameEntity>s; there's one array-element for each layer, and of course one list-element for each individual entity in a layer. IndicesUsed and IndicesUnused are corresponding arrays of List<int>s. Spawning an entity in a given layer puts it into the next available index in EntList[layer], as determined by the last element in IndicesUnused[layer] (or, if IndicesUnused[layer] is empty, it adds it to the end of EntList[layer]), and then adds the index to the end of IndicesUsed[layer], removing it from IndicesUnused[layer]. Removing an entity replaces it in EntList[layer] with a generic/empty GameEntity with a CreationFrame of -1, and moves its list-index from IndicesUsed to IndicesUnused. I've probably explained that really awkwardly.

When the game wants to cycle through all the entities for the purposes of Update(), movement, drawing, etc., it cycles through IndicesUsed, in order to keep them all in the order that they were created. And then it does GameEntity curEnt = EntList[layer][curIndex]; at the beginning of the loop in order to actually deal with all that.

An ObjID contains three integers: Layer, ListPos (its position in the layer, I'll rename that to ListIndex), and CreationFrame. The purpose of ObjID is for when the scripter wants to get at an entity after it's been spawned, i.e. if it's the boss and you want to move them around. The lookup in that case should take no more effort than simply checking if it exists, plus doing it the same way as iterating through the list.

Silver

The purpose of ObjID is for when the scripter wants to get at an entity after it's been spawned, i.e. if it's the boss and you want to move them around.

If that is the only use of an ObjID, wouldn't just using a reference be much simpler (and faster)?

Kimiko Muffin

/ Modified by Kimiko Muffin:

No. As I said above, using a Reference makes it impossible to change an entity's Type and still be able to keep track of it.

Meanwhile, the latest round of changes has resulted in a great big "man, whaaaaat" set of bugs when I try to use the default Draw() for GameEntities~ :D (EDIT: One of which was caused by the fact that I set the default Scale to 0F instead of 1F, HUZZAH)

Silver

Could you give an actual example? Because as far as I can see you shouldn't have a problem setting ObjID in the constructor, or passing it in as an argument or whatever.

Kimiko Muffin

Okay, let's say there are 9 extant entities in layer 11; however, EntList[11].Count == 12, because the bullets in indices 4, 1, and 3 were deleted earlier, in that order; thus, UnusedIndices[11] == { 4, 1, 3 }. (And let's say UsedIndices[11] = { 0, 2, 5, 6, 7, 8, 9, 10, 11 }.) You spawn a bullet in layer 11; because the last number in UnusedIndices[11] is 3 (actually, it might be quicker to remove it from the beginning, now that I think of it; the order really only matters for UsedIndices), the GameEntity is instantiated at EntList[11][3], and "3" is removed from UnusedIndices[11] and added to UsedIndices[11]. Shortly thereafter, the player collides with the bullet at index 9, which thus deletes itself. EntList[11][9] is set to a "blank" GameEntity with an ObjID that has a creation-frame of -1, and "9" is removed from UsedIndices[11] and added to the end of UnusedIndices[11].

The problem with establishing things in the constructor is that in C#, the only way to instantiate any kind of object without using Reflection is using a parameterless constructor.

I may have come up with a solution, though: an intermediary class, say EntityRef, which contains 1. its index and layer and creation-frame, and 2. a GameEntity reference. EntList would contain the EntityRefs instead of GameEntities; EntityRef.SetType<T>() would change the GameEntity instance to one of the proper type. GameEntity would still get to keep Pos, Vel, and Rot, but there wouldn't be any need for any read-only attributes set in the constructor (except for the ones handled by the Type : GameEntity), because all that could be handled by the EntityRef. And, of course, you're directly referencing the EntityRef. This could solve pretty much every problem mentioned here!

Silver

I was trying to think up something using the Factory pattern, but couldn't without adding a lot of unnecessary boilerplate (garrrr, C# generics Y NO TURING COMPLETE).

Using an intermediate like you suggested sounds like the simplest solution (though it really feels like reinventing the "pointer to a pointer")~

Kimiko Muffin

/ Modified by Kimiko Muffin:

Yeah, this is definitely an example of ... I'm half-remembering a quote that's something like "there are no right answers, only varying degrees of wrongness." Or "not the best way, just the only way" ...

EDIT: B'duh, I still have to somehow get a reference to the EntityRef into the GameEntity so it can do useful things to itself~ Ah well, I can still do static-type shenanigans.

Page 1 2 / Previous / Next / All