MobManager, working slimes, working Groovy plugin!

Cervator

Org Co-Founder & Project Lead
Contributor
Design
Logistics
SpecOps
Once again I can't help myself when I should be doing AI class homework :p

Just pushed out https://github.com/Cervator/Blockmania/ ... e831e27a0c which includes a new MobManager class that'll hold and update mobs in the world. Tested it with a working Groovy plugin that actually does something, puts a SlimeTool in the 4th tool slot, that spawns a slime in the world on left-click!

It works well, even :D



Crude prototype as usual that needs plenty of refactoring and what not. Got one more Manager to go and then I'll force myself to clean up stuff some. After I finish more startup stuff. After I finish AI class homework... okay, I'll be good now, off to learn some AI to put in the next Manager! :D
 

begla

Project Founder and Lead Developer
Contributor
Architecture
Logistics
Very cool Rasmus! That reminds me how rudimentary and ugly those slimes are. And how... stupid. :laugh: At least they are still working as they should.

Since I touched ConfigSlurper to replace the intial configuration class, I can not stop thinking about replacing this... ugly XML file for loading up the block type with a stylish Groovy script parsed by ConfigSlurper. Should be worth the effort. Thoughts?
 

Cervator

Org Co-Founder & Project Lead
Contributor
Design
Logistics
SpecOps
Totally in support, was going to play with it myself sometime, but you beat me to it. Go for it :)

The exact mob doesn't really matter, just being able to tinker is good enough! Next up: different color slimes to differentiate mobs for fun and technical experimentation. Somebody keep PETA away!
 

begla

Project Founder and Lead Developer
Contributor
Architecture
Logistics
I'm currently thinking about how I should implement this system exactly. I don't like the approach of parsing thousands of separate parameters and finally creating a Block object. This would be like a XML file, just worded using the Groovy config file format.

What about a separate folder filled with Groovy scripts, whereas each file contains a class definition inheriting from the base class Block. This folder could be dynamically scanned for Groovy files at startup and integrated into the BlockManager. This is quite like the initial approach, but provides easy addition and modification of blocks. Users could even overwrite existing methods in the block files to add different foliage colors and so on. :)
 

Cervator

Org Co-Founder & Project Lead
Contributor
Design
Logistics
SpecOps
I like it - had thought about something similar myself. Makes me think back on the bits of discussion prior to putting into XML - where you could (just like creatures) have some infrastructure classes in Java, then load details out of Groovy where indeed you could even use a class object extending or implementing the lowest fitting infrastructure class in Java.

Then you've got your native blocks in one directory, that will always update from the core code, and "plugin" blocks in a separate directory that have load preference to the native blocks. So customizing some block details is as easy as copy pasting the block file from native to plugin, then making your changes.

To absolutely minimize the content of the files, perhaps to even take out the class definition entirely (which makes it so you have to follow a more rigid structure in the file), you could perhaps use a subdir setup where each Java infrastructure class corresponds to a directory. So all the "plant" blocks go in a /blocks/plants directory (should there be something unique about plant blocks - like "growth" when exposed to sunlight). For hybrid plant/something else blocks you could still just make that particular block implement an interface to gain a second "characteristic"

Whatever way would need to make more sense than XML (easy) but also more than the original blocks-in-Java setup. Groovy has a light-weight advantage, but if we end up with all class objects in Groovy with some overlapping details instead of classes in Java I'm not sure if that makes much of a difference.

I'm not sure exactly which is the optimal approach, but I suppose we can tinker over time :)
 

begla

Project Founder and Lead Developer
Contributor
Architecture
Logistics
I've just pushed a first prototype that replaces the current block loading strategy. But this shows how slow Groovy can be in comparison to native Java code. :( Chunk generation takes up almost 1 to 2 seconds now instead of the 10-50 ms using the old approach which is unbearable. I'll have to test other approaches, which will be much less stylish. The access of block properties is the most time-critical part of the engine. Maybe I've just overlooked a better way to parse the scripts (maybe using the class loader and another class definition format is faster?). I'll see.

Just to showcase how simplistic this approach is:

Code:
package com.github.begla.blockmania.data.blocks

import com.github.begla.blockmania.blocks.Block
import com.github.begla.blockmania.blocks.Block.SIDE
import org.lwjgl.util.vector.Vector2f

def block = [
        getTextureOffsetFor: { SIDE side ->
            if (side == SIDE.TOP || side == SIDE.BOTTOM)
                return new Vector2f(6, 5)
            return new Vector2f(3, 2)
        },
        getTitle: { return "Bookshelf" },
        getId: { return 27 }
] as Block

blockManager.addBlock(block);
Those files just have to be put into the designated folder and... voilà.
 

begla

Project Founder and Lead Developer
Contributor
Architecture
Logistics
Okay. I've pushed another approach. Instead of scripts classes a dynamically loaded and instantiated via the Groovy class loader. Performance is better, but still way too slow. I*ve also replaced the fancy closures with ordinary method calls. :(

This is most certainly resulting from the overhead of Groovy's dynamic nature (reflection and stuff). Should not be much when used normally, but it sums up when there are 16x16x256 calls within one chunk.

EDIT: I will add a more or less hybrid approach in the next try. Many blocks that are currently available make up large parts of the terrain and are thus accessed quite often. For those primitive blocks, which do not provide any additional functionality besides some coloring based on their location, I'll create a ConfigSlurper script that initializes those blocks using getter and setter methods instead of subclassing. If any additional functionality is needed, it can be added via subclassing using the plugin architecture later on. Here the performance should not really matter that much, since those special blocks do not occur that often in the world.
 

Cervator

Org Co-Founder & Project Lead
Contributor
Design
Logistics
SpecOps
Hmm, pity. Although, there you're keeping Groovy objects around to reference real-time during the game? I was thinking the Groovy objects would just be used during game load only, not constantly. As in, for each "type" of block (plant, normal, liquid, whatever) there's a Java class and an associated directory with block objects in Groovy. During game start-up you tell a BlockManager to load blocks, which for each class goes to that dir and instantiates a Java class / fills in a map with details from the config stored in Groovy or so. When actually referencing block data during the game you're in pure Java.

I figured Groovy would be slower if used for really core operations, thus the desire to just use it for config and light-weight data

Does that make sense / any difference?
 

begla

Project Founder and Lead Developer
Contributor
Architecture
Logistics
Cervator said:
During game start-up you tell a BlockManager to load blocks, which for each class goes to that dir and instantiates a Java class / fills in a map with details from the config stored in Groovy or so. When actually referencing block data during the game you're in pure Java.
That's actually what I'm doing at the moment. Going through the directory and instantiating Java objects from the Groovy class definitions. So we get pure Java objects after the initialization process. But they seem to get wrapped into some kind of proxy object (worked myself through the Groovy forums a bit) when the Java object is created from the Groovy class definition. That makes the small performance difference, which becomes ugly with large amounts of data (like our chunks).

Relying completely on Groovy objects in the most time critical part of the engine was not my best idea. But as I described... I have got a more appropriate workaround for that. Hopefully. :)
 

Cervator

Org Co-Founder & Project Lead
Contributor
Design
Logistics
SpecOps
Huh, I know Groovy gets fancy when it is doing its short-hand thing for doing stuff within Groovy, but weird to hear Java classes get wrapped in something. Maybe if the Groovy classes infer something fancy, that causes something extra for Java?

I wonder if that wouldn't be avoidable by not declaring Groovy classes outright, but instead just using ConfigSlurper in parsing the data out of plain Groovy files to pass to a Java instantiation? Of course that might make it tricky to get any use out of a tree-like structure of block types in the config - or not, if the Java side is smart enough to load some "defaults"

Anyway, good to hear you've got ideas on it, cool. Have fun :)
 

begla

Project Founder and Lead Developer
Contributor
Architecture
Logistics
Cervator said:
I wonder if that wouldn't be avoidable by not declaring Groovy classes outright, but instead just using ConfigSlurper in parsing the data out of plain Groovy files to pass to a Java instantiation? Of course that might make it tricky to get any use out of a tree-like structure of block types in the config - or not, if the Java side is smart enough to load some "defaults"
That'll be the next prototype which will hopefully remove the slow motion chunk generation. ;)
 

begla

Project Founder and Lead Developer
Contributor
Architecture
Logistics
Behold... The new Groovy script for initializing Blocks:

Code:
package com.github.begla.blockmania.data.blocks

import com.github.begla.blockmania.blocks.Block
import com.github.begla.blockmania.blocks.Block.BLOCK_FORM
import com.github.begla.blockmania.blocks.Block.COLOR_SOURCE
import com.github.begla.blockmania.blocks.BlockManager
import org.lwjgl.util.vector.Vector2f
import org.lwjgl.util.vector.Vector4f

def BlockManager bm = blockManager;

// The air we breathe
bm.addBlock(new Block().setTitle("Air").setId((byte) 0).setDisableTesselation(true).setTranslucent(true).setInvisible(true).setBypassSelectionRay(true).setPenetrable(true).setCastsShadows(false).setRenderBoundingBox(false).setAllowBlockAttachment(false).setHardness((byte) 0))
// <--

// Etc.
bm.addBlock(new Block().setTitle("Bookshelf").setId((byte) 27).setTextureAtlasPosTopBottom(new Vector2f(6, 5)).setTextureAtlasPosMantle(new Vector2f(3, 2)))
// <--

// Liquids
bm.addBlock(new Block().setTitle("Water").setId((byte) 9).setTextureAtlasPos(new Vector2f(15, 12)).setTranslucent(true).setPenetrable(true).setRenderBoundingBox(false).setHardness((byte) -1).setAllowBlockAttachment(false).setBypassSelectionRay(true).setCastsShadows(false).setLiquid(true).setBlockForm(BLOCK_FORM.LOWERED_BLOCK))

bm.addBlock(new Block().setTitle("Lava").setId((byte) 10).setTextureAtlasPos(new Vector2f(15, 15)).setTranslucent(true).setPenetrable(true).setRenderBoundingBox(false).setHardness((byte) -1).setAllowBlockAttachment(false).setBypassSelectionRay(true).setCastsShadows(false).setLiquid(true).setBlockForm(BLOCK_FORM.LOWERED_BLOCK))
// <--

// Ice
bm.addBlock(new Block().setTitle("Ice").setId((byte) 8).setTextureAtlasPos(new Vector2f(3,4)).setTranslucent(true))
// <--

// Glass
bm.addBlock(new Block().setTitle("Glass").setId((byte) 26).setTextureAtlasPos(new Vector2f(2, 2)).setTranslucent(true))
// <--

// Soil
bm.addBlock(new Block().setTitle("Dirt").setId((byte) 1).setTextureAtlasPos(new Vector2f(2, 0)))

bm.addBlock(new Block().setTitle("Grass").setId((byte) 6).setTextureAtlasPosMantle(new Vector2f(3, 0)).setTextureAtlasPos(Block.SIDE.TOP, new Vector2f(0, 0)).setTextureAtlasPos(Block.SIDE.BOTTOM, new Vector2f(2, 0)).setColorSource(COLOR_SOURCE.COLOR_LUT))

bm.addBlock(new Block().setTitle("Snow").setId((byte) 7).setTextureAtlasPosMantle(new Vector2f(4, 4)).setTextureAtlasPos(Block.SIDE.TOP, new Vector2f(2, 4)).setTextureAtlasPos(Block.SIDE.BOTTOM, new Vector2f(2, 0)))

bm.addBlock(new Block().setTitle("Sand").setId((byte) 2).setTextureAtlasPos(new Vector2f(2, 1)))
// <--

// Wood
bm.addBlock(new Block().setTitle("Wood").setId((byte) 4).setTextureAtlasPos(new Vector2f(4, 0)))

bm.addBlock(new Block().setTitle("Tree trunk").setId((byte) 5).setTextureAtlasPosTopBottom(new Vector2f(5, 1)).setTextureAtlasPosMantle(new Vector2f(4, 1)))
// <--

// Stone
bm.addBlock(new Block().setTitle("Stone").setId((byte) 3).setTextureAtlasPos(new Vector2f(1, 0)))

bm.addBlock(new Block().setTitle("Cobble stone").setId((byte) 19).setTextureAtlasPos(new Vector2f(0, 1)))

bm.addBlock(new Block().setTitle("Hard stone").setId((byte) 18).setTextureAtlasPos(new Vector2f(1, 1)).setHardness((byte) -1))

bm.addBlock(new Block().setTitle("Brick").setId((byte) 20).setTextureAtlasPos(new Vector2f(7, 0)))
// <--

// Billboards
bm.addBlock(new Block().setTitle("Red flower").setId((byte) 16).setTextureAtlasPos(new Vector2f(13, 0)).setTranslucent(true).setPenetrable(true).setTranslucent(true).setColorSource(COLOR_SOURCE.FOLIAGE_LUT).setBlockForm(BLOCK_FORM.BILLBOARD))

bm.addBlock(new Block().setTitle("Yellow flower").setId((byte) 17).setTextureAtlasPos(new Vector2f(12, 0)).setTranslucent(true).setPenetrable(true).setColorSource(COLOR_SOURCE.FOLIAGE_LUT).setBlockForm(BLOCK_FORM.BILLBOARD).setAllowBlockAttachment(false))

bm.addBlock(new Block().setTitle("High grass").setId((byte) 13).setTextureAtlasPos(new Vector2f(12, 11)).setTranslucent(true).setPenetrable(true).setColorSource(COLOR_SOURCE.FOLIAGE_LUT).setBlockForm(BLOCK_FORM.BILLBOARD).setAllowBlockAttachment(false))

bm.addBlock(new Block().setTitle("Medium high grass").setId((byte) 14).setTextureAtlasPos(new Vector2f(13, 11)).setTranslucent(true).setPenetrable(true).setColorSource(COLOR_SOURCE.FOLIAGE_LUT).setBlockForm(BLOCK_FORM.BILLBOARD).setAllowBlockAttachment(false))

bm.addBlock(new Block().setTitle("Large high grass").setId((byte) 15).setTextureAtlasPos(new Vector2f(14, 11)).setTranslucent(true).setPenetrable(true).setColorSource(COLOR_SOURCE.FOLIAGE_LUT).setBlockForm(BLOCK_FORM.BILLBOARD).setAllowBlockAttachment(false))
// <--

// Cacti
bm.addBlock(new Block().setTitle("Cactus").setId((byte) 28).setTextureAtlasPosTopBottom(new Vector2f(5, 4)).setTextureAtlasPosMantle(new Vector2f(6, 4)).setBlockForm(BLOCK_FORM.CACTUS).setDisableTesselation(true).setTranslucent(true))
// <--

// Minerals
bm.addBlock(new Block().setTitle("Coal").setId((byte) 21).setTextureAtlasPos(new Vector2f(2, 2)))

bm.addBlock(new Block().setTitle("Diamond").setId((byte) 25).setTextureAtlasPos(new Vector2f(2, 3)))

bm.addBlock(new Block().setTitle("Gold").setId((byte) 24).setTextureAtlasPos(new Vector2f(0, 2)))

bm.addBlock(new Block().setTitle("Red stone").setId((byte) 23).setTextureAtlasPos(new Vector2f(3, 3)))

bm.addBlock(new Block().setTitle("Silver").setId((byte) 22).setTextureAtlasPos(new Vector2f(1, 2)))
// <--

// Light sources
bm.addBlock(new Block().setTitle("Torch").setId((byte) 29).setTextureAtlasPos(new Vector2f(0, 5)).setTranslucent(true).setPenetrable(true).setBlockForm(BLOCK_FORM.BILLBOARD).setLuminance((byte) 15).setAllowBlockAttachment(false))
// <--

// Leafs
bm.addBlock(new Block().setTitle("Leaf").setId((byte) 11).setTextureAtlasPos(new Vector2f(4, 3)).setTranslucent(true).setDisableTesselation(true).setAllowBlockAttachment(false).setColorSource(COLOR_SOURCE.FOLIAGE_LUT))

bm.addBlock(new Block().setTitle("Dark leaf").setId((byte) 12).setTextureAtlasPos(new Vector2f(4, 3)).setTranslucent(true).setDisableTesselation(true).setAllowBlockAttachment(false).setColorSource(COLOR_SOURCE.FOLIAGE_LUT).setColorOffset(new Vector4f(0.8f, 0.8f, 0.8f, 1.0f)))
// <--
This is my initial milestone for this refactoring step. It might need some work to look more pretty and allow a more hassle-free configuration. But it is already much more versatile than the initial XML file and also as fast as it can be.

Blocks can be also extended using subclassing (thinking of situations when we get blocks that do a bit more than being static). I'm starting to adore Groovy btw. :)
 

Cervator

Org Co-Founder & Project Lead
Contributor
Design
Logistics
SpecOps
begla said:
I'm starting to adore Groovy btw. :)
Muahaha, my nefarious plan unfolds! ;)

I like planting ideas. Thought there was something to Groovy, but didn't really have the time to go nuts with it. Happy to see that it is working out :)

So now it is full-speed even though it still looks like Groovy itself is being used to do the loading? Cool - I guess that makes sense if it just loads it into a Java class.
 
Top