lists and eachin questions

Monkey Forums/Monkey Beginners/lists and eachin questions

Ashmoor(Posted 2016) [#1]
I have a list
gameObjectList<gameObject>
and two classes - gameObject and cPoint extends gameObject,

why can't I use For local o:cPoint=eachin gameObjectList to go through only the cPoint objects of the list?


Gerry Quinn(Posted 2016) [#2]
Because 'Eachin' is set up to go through all the objects in the list, so you must specify a gameObject class. Under the hood a gameObject enumerator object is created, I think. I guess it would be possible for the language to overload Eachin so that it works the way you want, but it's not hard to use it as it is:

I don't do much type conversion, but as far as I know the following is the way to go - there's no 'IsKindOf' keyword, but a casting failure returns null.

For Local gob:gameObject = Eachin gameObjectList
	cPoint cp = cp( gob )
	If cp <> Null
		' Your code here
	End
Next



Jesse(Posted 2016) [#3]
I think someone meant this:
For Local gob:gameObject = Eachin gameObjectList
	Local cp:cPoint = cPoint( gob )
	If cp <> Null
		' Your code here
	End
Next


or you can also do it like this:
Local nd:list.Node<gameObject> = gameObject.FirstNode()
While nd
   Local cp:cPoint = cPoint(nd.Value())
   If cp
       'your code
   Endif
   nd = nd.NextNode()
Wend



more verbose but faster.
Several advantages but not going into detail.

also I haven't tested it but I believe you can do this:
For Local cp:cPoint = Eachin gameObjectList
	If cp <> Null
		' Your code here
	End
Next



ImmutableOctet(SKNG)(Posted 2016) [#4]
First off, welcome to the forums. I know I'm a bit late in saying that. What you'll find below is a detailed explanation of how to manage objects, and the details of type manipulation in Monkey. If you're not interested, and just want to cast your objects, the above posts are good solutions.

Remember, I warned you:

What Jesse and Gerry Quinn say is correct. There's runtime overhead associated with dynamic casts, and that's one of the many reasons why they should be avoided. If you're dealing with a relatively small number of objects, then it's really up to your situation. Either way, in most languages, a dynamic lookup is considered bad practice, as it adds significant runtime costs, and doesn't play well with the idea of encapsulation. There's also environments where RTTI isn't available at all, or is avoided as best practice, but that's not so relevant here.

This doesn't mean multiple containers is the answer, as that could technically be slower for different reasons. In most cases, casting will be slower, as you don't normally have to worry about worst-case caching scenarios. Another thing to note is that your target matters. Though dynamic casts are slow in most cases, some targets are built around the ideas they bring to the table. The HTML5 (JavaScript) target is a good example of this. JavaScript doesn't technically have types, so the overhead could theoretically be accounted for. At the same time, JavaScript implementations today are very good at running straight forward code, so there may be something behind optimizing anyway.

The two problems here are access and efficiency. On one hand, passing the same data at the base of the class hierarchy is faster (Virtual calls). On the other hand, this is more restrictive, and doesn't allow for specialization. There's a few ways to tackle this problem using your own meta-data (Object types), but there's other fish to fry.

Below I discuss the details of polymorphism, as well as ways to work with it in games. The ideas may or may not be interesting, but they discuss how things could be done at larger scales. If you're not interested, feel free to stop reading. The gist is that when you build your program, you don't know any extra information about an object. In this case, you're giving information by using the argument <gameObject>, which lets the compiler know ahead of time what type to use. The only way to know this for any given object is to perform a dynamic cast, as described by Jesse and Gerry Quinn. This isn't the only way to go about this, but it's the only way without rethinking some things. These are details that matter for large scale projects, not smaller games. Although, I do talk about how polymorphism works, which may be interesting:


--------------------------------------------------------------------------------------------------------------------------------

With any game engine, one of the biggest bottlenecks is memory bandwidth. The better the reduction of accesses to an object, the better. In Monkey, some of the lower-level ways to solve this problem aren't viable, so we need to look at the cleaner options.

First, let's look at the "one container to rule them all" strategy. With one container, we have three ways to interact with our objects: Virtual calls, type casts, and relations. I've gone over type casts well enough already, so there's no point repeating myself. I guess the next thing to talk about would be virtual method calls:

In Monkey 1, a virtual call is always implied when dealing with this sort of setup. A virtual call is done by following a pointer the compiler gives each object for its class-type. This points to a dedicated 'table' in the program's embedded storage. Think of this like a 'StringMap'; it maps names (Or similar) to each version of the method.

In the following situation, 'X' defines the method 'Test', and 'Y' overrides it by taking the name, and providing its own version:



By doing this, a 'table' of virtual methods has to be created, and any time the 'X' class's 'Test' method is called, the object's virtual-table needs to be accessed. This doesn't have to be the case under some situations, but it's the usual path taken. Given the assumption we only had (Or used) one class, then a virtual call would not be needed.

My thoughts aside, Monkey 2 does seem to favor explicit uses of 'Virtual' and 'Override'. These aren't relevant to Monkey 1, so I'm not going to dwell on them. The idea here is that base classes need to make virtual calls, and they're a normal part of polymorphic behavior.

Not sure what polymorphism is? Well, you know what it is now. In essence, it's the idea that an object's behavior (Method implementations in most cases) is dictated by the class used to create it. Looking at my example, since 'Y' extends 'X', 'Y' can be implicitly used in place of 'X' in most situations. This is pretty cool, and it's a major benefit to object oriented programming.

Following this logic, the example you've shown doesn't make a lot of sense. In your case, 'cPoint' extends 'gameObject', but you can't use the 'cPoint' type directly, since different types can extend 'gameObject'.

Back to the original discussion, we need to figure out how to access our types' behaviors without using them directly. The common way to do this is a virtual call, but that doesn't cover everything. You see, virtual calls need the same signature to work. They need not only the same name, but also the same return-type, and the same arguments. In the example above, I had the return-type of 'String', and no arguments. This can be overridden by inheriting classes, like 'Y', but the signature can't change. As a side note, a "signature" is best known by programmers as a "prototype", but since it's less confusing, I'll say "signature".

Before I continue, I should say what an overload is. In many programming languages, an overload is a method that takes the name of another, but not its exact signature. So, you could have two distinct versions of 'Test', one that takes no arguments, and one that takes many. As long as the arguments are in some way different, it's a valid overload. This also means two versions of 'Test' with no arguments, but different return-types won't work. You would need some kind of difference in the data passed to the method to identify it as something else with the same name. If they are indeed considered different overloads, then you can return a different type.

To reduce confusion, Monkey 1 does not allow for overloads to be described in extending classes. This means 'Y' can't use the name 'Test' for something 'X' didn't approve of. It can, however, override any of the method signatures described in 'X'. This limitation is a choice that may have been redacted in Monkey 2, not sure about that one. There's work in place to make that less confusing, though.

It's just a language quirk. Regardless, it's good to know one of the first pitfalls a new programmer may have with Monkey: Overloads are different methods. This means all the polymorphic behavior I've talked about still applies to other overloads, but individually. Something cool to take note of is the ability to call other overloads, and to call your super-class's method implementations via 'Super'. Both of these ideas are useful, especially when sharing code between overloads.

Another good tip to bring up is the 'Final' keyword, which can be used on classes or methods you don't want to be extended or overridden. In addition, properties are methods, so they follow a lot of the same rules.


--------------------------------------------------------------------------------------------------------------------------------

Okay, back to the original challenge: Object access.

Virtual methods are great for personal behavior, given a useful specification. In a game, you could pass different objects into an 'Update' method, for example. This is far better than using fields for everything, as you don't have to stack extra storage requirements on your objects. Or have the need to clean things up. With that in mind, however, fields are a way to remove the limitations of virtual method signatures. The storage details should be understood, though. You shouldn't be referencing some giant strand of objects when you're not supposed to. This is a pseudo memory-leak when unintentional.

Alright, we've established that virtual methods alone are pretty good for things like individual update routines, and other encapsulated behavior, but not everything. What about communication between objects? These are your collision routines, your interactions, and any other details your game cares about.

Think of the original idea using one list. In this situation, if you wanted to check if one object was near another one, you would need to search through every single one, trying to find this out. Sounds straight forward, right?

Well, not quite. Imagine you had 4 objects. Each object makes a check for every other object. This means your simple distance check would need to be done on each object, against every other object. This means the check is exponential: 1 Checks 2, 3, and 4, then 2 checks 1, 3, and 4, and so on... This doesn't factor in the cost of a virtual call, something that isn't a lot on newer systems, but still adds up. There's good ways of reducing virtual calls, but I'll touch on that at the end. The point I'm trying to make is that it's a lot of work to brute-force this. Considering you have ~16ms to get things done, you need to save time wherever you can.

Imagine a game world with 100 objects that care about each other, and could do a plethora of things. It's not hard to see the bottleneck. I should also bring up one of the benefits to centralizing parts of your codebase, and that's test reduction. In the example I gave about the four objects, time could be saved by mapping which objects have been checked. This is a decent way to reduce complexity, and is useful for small scenes.

That's one approach, but what else can we do? Can we add the first idea into it? The answer is usually a tree of some sort. Simply put, you generate a tree by sectioning your game world into quadrants. Each quadrant has its own container like your 'gameObjectList' above, and the best part is, you can break it into more quadrants. You can do this by having each quadrant hold four new areas inside of it. Those too could have quadrants recursively. You can then check against these smaller lists instead of a giant list. At the same time, however, you could have a master list to manage which objects are in the world, and generate your tree periodically.

This is called a quadtree, and it's what's commonly used for 2D collision, as well as game logic. There's similar strategies, so it's really about what you're doing. For reference, in 3D games and simulations, this is called an octree.

This strategy could also work for a segregated system, where you have different lists and/or trees, and you use them to have your objects interact at different distances and detail levels. Not only that, but you could implement an "actor" system on top of your existing codebase, and have traits and virtual methods specific to "actor" objects.

At any rate, there's many ways of handling exponential problems like this, so it's best to build around your expected load. Want a lot of objects? Try a tree-based approach. Only need a few? A standalone list might just be all you need. There's no one answer, but brute-forcing game logic isn't always viable.

This post only covers the semantics of working with objects. For details of memory allocation and bigger optimizations, I'd have to dig up some posts.

--------------------------------------------------------------------------------------------------------------------------------

I mentioned this before, but I also recommend reducing virtual calls where practical. This means you shouldn't have a ton of virtual calls to do what one call could do. Or better yet, a managing class. Some behavior doesn't need to be in a virtual method. For example, you don't even need to use them for rendering. You could have a camera system that handles this for you. That would be faster than using virtual calls everywhere. The best sentiment is that they're useful, but not always required. I've fallen into that trap many times before.

At the end of the day, virtual overhead is an implementation detail in Monkey, but it's good to know how to optimize. If you stuck it out this long, thanks for reading.


Ashmoor(Posted 2016) [#5]
Wow, thank you for the replies.

Type casting looks interesting, I must do some further research to get a good grip on these things.

@ImmutableOctet(SKNG)
"First off, welcome to the forums."
Thanks.

I read the whole text, thanks again for the effort. I will need a while to understand all you are saying.

My problem comes from BlitzMax, I could do it there and thought it was something standard . Apparently in monkey I can only access the fields of the gameObject class while in Bmax I could access the fields of the extended class by calling the eachin loop like I said above.


Gerry Quinn(Posted 2016) [#6]
You can access the fields of the extended object so long as you actually *have* an extended object. The gameObjectList has a bunch of gameObjects, soe of which may be extended and some not. The point of the casting in the above is to (1) determine whether the particular gameObject instance you are looking at *is* actually a cPoint object, and (2) tell the compiler that you are accessing it as a cPoint object. [If you only wanted to use base class methods, you wouldn't care whether it was a cPoint or not.]

You can do (1) in other ways - for example, your gameObjects could each contain an int field which has a different constant value for each type of object. Using that, you could roll your own 'IsTypeOf' method, so that you can check without casting if any given game object is a cPoint. But you are really just duplicating what the cast does under the hood; all objects do secretly have such a value, though the details could vary with the target language.

You can't avoid (2) though - it's just a matter of language syntax. You can't call methods specific to cPoint using a gameObject reference, because not all gameObjects have these methods.