junkdog / artemis-odb

A continuation of the popular Artemis ECS framework
BSD 2-Clause "Simplified" License
770 stars 109 forks source link

Provide an Artemis networking example #103

Closed DaanVanYperen closed 9 years ago

DaanVanYperen commented 9 years ago

I've been wanting to make simple little coop games with Artemis since forever.

Wouldn't mind making a reference game that demos basic networking on Artemis. While it's probably easy to hack something together, i'd like to build something in the spirit of ECS that'll be reusable. I'm looking for your thoughts and guidance on this.

Ideally it would provide a starting point for people who just need some basic real time or turn based state synced and are as intimidated by networking on an ECS system as I am. ;)

I know you've said you're not very experienced with networking, but you might have some good high level ideas about it from perspective of Artemis.

Just to give you something to shoot at, thinking of an authoritive server that sends delta changes in components to clients, with the clients sending back control events to the server, via a library like Kryonet.

Client+Server

Client

Server

No clue yet about entity life cycle, creation/deletion of entities and components with networked traits. We could track lifecycle and possibly component additions somehow in artemis fully automated, or instead just use some events to let the user deal with creation-destruction. I assume Artemis is deterministic for the most part? (Except for UUIDs).

Let me know what you think / prefer.

@timtips I might be making this up, but didn't you have some experience with networking on Artemis? Would love to have your input as well.

junkdog commented 9 years ago

I'll write a more extensive reply later - waiting for my dev environment to boot up at work.

I assume Artemis is deterministic for the most part?

Ids for systems and components depend on initialization order, otherwise it's (mostly) deterministic. I'm thinking there should be some concept of Slave Worlds which get entity ids from a Master World (ie, com.artemis.World as it is now). There's already support for nesting worlds, so it shouldn't be too difficult.

What data/components to send could probably be controlled by annotations/interfaces, as some could be recreated at the client side.

Edit: UUIDs are pretty expensive, 128 bits each - I intend to replace it with a simple long (while tracking explicitly declared unique ids).

junkdog commented 9 years ago

"so it shouldn't be too difficult" - possibly famous last words.

DaanVanYperen commented 9 years ago

I'll write a more extensive reply later

Bwha. Bwahahahaha! I have trapped you! You will never leave this issue!

Ids for systems and components depend on initialization order

Ah good point, can assume there will be shared, server and client specific components, so you'd need an alternative way to uniquely identify types.

I'm thinking there should be some concept of Slave Worlds which get entity ids from a Master World (ie, com.artemis.World as it is now). There's already support for nesting worlds, so it shouldn't be too difficult.

Can you elaborate a bit where the nesting comes in? I'm assuming you're seeing a possibility to optimize.

For the example the hosting client runs a server in a separate thread, and for singleplayer mode the game still runs the server, just to keep things from diverging.

Figured both client and server would share the networked components in a common module. You might be right that interfaces might be more flexible, or at least allow the client and server components to diverge. Maybe a network identity for components is enough? Probably will still work with annotations to configure the way things are synced. Doubt we'll do much GWT with networking. right? RIGHT? ;_;

The server would be master world like you said, and instruct clients to instance entities with a specific network identity. Not sure what would be good for that, I can imagine UUID isn't the cheapest network traffic.

jartur commented 9 years ago

I have made a server for a game with Artemis. Unfortunately I can't share the result and even the name of the game for now, but I can share a little of my experience with this.

Basically, I have "hacked something together" and it would be awesome if you can make a reusable core for this. What I did is I have two systems, one that handles all input and one that produces output. All input is decoded from network representation into small event objects which are fed into the game from external threads and a enqueued into a blocking queue that the Input system polls and if it has input it just processes it as if it would do if that input was from keyboard or whatever. It doesn't concern itself with any networking. In the Output system I form another set of event objects that are fed into an event sink that will encode them into their network representation and send to all interested clients. Every frame I send almost full world update for players' positions and stuff like that that changes very frequently. All other systems when they produce some one-time events (like pick up something, or shoot someone) create an entity that has a GameEvent component which Output system is subscribed to and it gathers all these entities and adds them to the update and deletes them from world. This works very well for me. I should add that server may run up to 500 games simultaneously and the client doesn't use any ES framework so we have rather different code and custom networking with Google protobuf.

Also, all the interaction with game from the outside code is asynchronous, of course.

Namek commented 9 years ago

OMG Very good topic! Something super general is hard to be done. I've been struggling myself with desire to implement such reusable network system. I've spent a lot of time to design such thing and... I failed. One of my reasons was security - which leads to authoritative server. To be more precise - it always DEPENDS whether about some things should decide the server or a client. And when it depends... yeah, it's hard to implement something reusable.

(De)Serialization is the second thing which almost killed me. When you implement a game you need custom many packets/messages. You would need some efficient serialization system or library like protobuf. But here appear problems with reflection under GWT :(

State delta, prediction, interpolation and such things are often needed not only for Health Points. So there is a need for some kind of general util. Maybe some "Network Property" which contains value, gain speed, loss speed, max gain, max loss, interpolation mode (easing - for hp bar animation?) and probably something more.

One more thing about prediction. It's not always obvious how to predict values. Sometimes it's position with speed, but sometimes you need to consider physics and collisions into it. But it's not even always position and speed, it may be character state/animation. When it comes to predictions there is always some little amount of pseudo-AI.

Of course there is always a data which has to be updated and data which doesn't have to when there is newer one. I mean lossy UDP and things such as a position of driving car (the newest update is only important). There comes next problem - how to mark data that need to be received properly? RPC? RPC such because of security IMHO.

kryonet seems to be a nice library but I didn't want to create a strong dependency on top of it. I suggest not going this path. It's much easier to have such dependency instead of fighting with it.

I'm not convinced about that same entities should be synced through artemis UUIDs. I've seen it in Unity and it sucked sometimes. Example: your unit died on server but you firstly want to show some die animation on client and disappear it only after that animated boom moment. Imagine that unit (entity) disappears during animation, well, it shouldn't do that. Semi-solution? Clone unit for boom animation... I don't like it. What do you think? IMHO It's better to kill entities manually.

Packet prioritization on Server or Client. I'm not sure how to deal with it due to performance problems. When you update object state then it's easy - you just save sequence number of last used update packet. When there appears some lost old packet with lower sequence number then it should be just thrown away. But consider some two EVENTS like SHOOT and DIE. How do you treat such two messages when some character wants to shoot and should die? How do you find about that this shot shouldn't happen because character plays DIE animation in the same time? It's not best example of it but I just wanted to note prioritization problem - I don't believe it can be generalized and it's not hard to achieve lack of possibility to do that manually in some general Input/Output system which @jartur talked about.

My last word for today - I think it would be best to list all required network implementations of currently existing games and then try to join all those different features into one thing. @DaanVanYperen already made some short list but that was not too detailed (I believe that I have shown this fact in this post).

DaanVanYperen commented 9 years ago

Love the different viewpoints so far, keep it coming! Will reply in detail when I have a chance.

Digested notes @jartur

@Namek

Overall there seems to be a call for.flexibility. I'm going to have to be pragmatic in this and make some choices to save on development time. but I'm all for taking care things can be swapped out whenever possible.

Going for the juicy core here, supporting everything from high speed FPS games to heavy voxel games will probably derail this really quickly!

Still, want to do it the ECS way, so hope there are some more insights on that!

DaanVanYperen commented 9 years ago

@Namek in your research did you come across any frameworks that came close to what you wanted to do?

Namek commented 9 years ago

Nope, not really.some libraries are interesting due to specific features. kryonet was interesting because it supports both TCP and UDP internally. UnityPark uLink has autosync of entities position (Every GameObject in Unity has a Transform). Lidgren supports a few reliability modes. There are more interesting libraries but back then I haven't found anything such flexible. And I am not sure how to deal with ECS here, too.

I'm interested to achieve something flexible enough to be used in both shooter network logic and turn-based games. Also I'm not convinced that using ECS would help much. How do you want to involve ECS into networking?

timtips commented 9 years ago

@DaanVanYperen I don't know how valuable my experience with networking is for this topic. I used deterministic lock-stepping for holowars (which I didn't touch for months now) with artemis which circumvents some of the problems mentioned here (no need for an authoritative server, no inter/extrapolation because no need for real-time updating). Of course it raises a lot of other problems that need to be solved (turns out making a complex java game deterministic isn't as easy as one might think) and I wasn't able to find an example for lock-stepping methods that goes beyond abstract descriptions (of course there's stuff like http://www.gamasutra.com/view/feature/131503/1500_archers_on_a_288_network_.php but from there to a real implementation wasn't as easy as I thought). So ideally I should write a very simple game to demonstrate how I did it. I'll consider doing something like that for the next non Ludum Dare jam I participate in.

I also created a platformer game that never made it into public that worked with box2d physics and an authoritative server to sync entities across clients. It worked pretty well with local interpolation and synchronization of server data. I don't remember why I stopped working on it.

I used kryonet for all my multiplayer games and have never experienced any problems with it. It was very fast when used with UDP packets for real-time synchronization. In the case of holowars (deterministic lockstep) it didn't matter too much as all you transfer is user input.

Some thoughts on some topics touched here from those two games:

UUID for entity reference across simulations

I actually started to differentiate between networked and non networked entities at some point. Networked entities were created with an identifier (d-type: long) that I used to reference entities across clients. I think I used managers to access entities by their net-id. I also had non-networked entities (particles and non-critical things). This could probably be done with a component and shouldn't be an attribute of the entity itself.

I assume Artemis is deterministic for the most part? (Except for UUIDs)

In my experience it is. I think I ran into trouble with float deltas and had to force strict modes on all classes that dealt with with times so eventually I changed my utility classes like Timers to use integers but it is very likely that this isn't necessary if you make sure (and can afford) to enforce strict mode for determinism.

Going for the juicy core here, supporting everything from high speed FPS games to heavy voxel games will probably derail this really quickly!

I am curious what a "general" ECS networking framework would look like. I feel like my personal solutions were very customized to the specific game type and system behavior. I found the combination of the data-driven nature of ECS together with a very easy to use and flexible library like kryonet (well, RPC calls feel a bit clumsy but overall it's super easy) already gave me the foundation for multiplayer games that I needed. I agree though, that some simple example games that demonstrate the beauty of ECS and network synchronization would be awesome.

Namek commented 9 years ago

I've just stumbled upon this: https://github.com/Dvergar/ECS-Networking-Haxe/tree/master/sample-openfl/Source

DaanVanYperen commented 9 years ago

I'm mainly looking for a solution that leverages Artemis' design and makes networking accessible out of the box for ecs/networking newbies. High level convention/annotation based networking of components, on top of some networking systems with a nice example or two, client/server system reuse. The actual networking framework can be completely outside ECS.

Still, will make concessions to keep things doable, Not aiming for a system that does everything for everyone. I'll be happy if people can easily prototype network games with it, Extreme needs will require a custom solution anyway

So if lockstep would fit best, but a client/server solution would cover that usecase, I'd eat the performance/memory cost and go with client/server.

Kinda charmed with how Natural Selection 2 (fps/rts) deals with it, Define a bunch of networked variables on mixins, specify things like constraints, Interpolation and the like, and the framework does the rest, Write manual event handlers for client changes,

DaanVanYperen commented 9 years ago

@junkdog Stealing your maven setup for artemis-odb-contrib! bwhoehaha. Now just need to figure out how to bribe someone for a spot on a maven repo. D:

Namek commented 9 years ago

Finally on PC! Writing from tablet was a nightmare.

Seems like we're not that far from each other. I don't want to make something-everything-for-everyone anymore. I'd like to have features like:

Some time ago I have tried to implement Quake 3 snapshot delta system: http://fabiensanglard.net/quake3/network.php for my old game which I'm reimplementing using artemis: http://www.namekdev.net/games/stickmen/

I'm pretty surprised, because of lack of time in last months, I have forgotten about that. I remembered just an hour ago when I looked at my class named "Snapshot". It looks like this:

public class Snapshot {
    int sequenceId;
    PlayerState[] playerStates;
    RocketState[] rocketStates;
    ...
}

public class PlayerState {
    public int horizontal = 0;
    public int vertical = 0;
    public boolean isJumping = false;
    public boolean isSprinting = false;
    public boolean isShootingMG = false;
    public boolean hasShotRocket = false;
    public boolean isLying = false;
    public long rocketShotTimestamp = 0;
    public float x, y;

    ...
}

public class RocketState {
    float x, y;
    short horizontal;
    short vertical;

    ...
}

And I already see that it's hard to automatically calculate delta for such specific Snapshot. Do you have any idea how to create non-specific system for delta calculation? Probably could be done through manual serialization... (EDIT: OK, now I remembered that snapshot delta algorithm does not need to know values or types, bits are enough to compare snapshots). However, I'm still not sure what to do with those boolean deltas but I'm sure that position and orientation will be needed.

And still, I'm curious what could be so specific for ECS here. If not state sync for given component then what? When properly implemented it actually doesn't matter whether it's Component or any other type. We need to just connect it wisely with sync-desired object.

Namek commented 9 years ago

Googling, googling and... https://github.com/benruijl/quakemonkey Wow, that's pretty interesting.

DaanVanYperen commented 9 years ago

Good post @Namek, will look at your links!

And still, I'm curious what could be so specific for ECS here. We need to just connect it wisely with sync-desired object.

That's basically all I'm suggesting. Take an existing network framework, integrate it in Artemis ECS pattern like you would any externalized framework, and provide enough sauce so it both becomes a drop in solution and a great reference for prototype networking with Artemis.

I agree with most of your goals, would probably initially do events over RPC but that's just my personal preference. FPS lag compensation in the sense of being able to rewind game state and determining where everything was goes a bit beyond my personal expertise, and will probably not be super critical for the types of games I personally make, though I certainly see how a data driven system is great to achieve historic snapshots.

The most high level sauce I expect will end up as an annotations and reflection solution on top of Junkdogs planned entity templating #104, which'll exchange speed for convenience, but we can provide a sufficiently exposed API to step down a level and gain performance and control. I realize that's a bit vague, but as a general intention it should suffice.

I'm not too worried about performance. We do have the option to do some compile time weaving of which @junkdog is a good source of knowledge, and for GWT we can do something similar.

I'm confident networking can be set up in such a way that it'll provide out of the box extra/interpolation and constraints on fields, and allow users to drop in specialized extra/interpolator classes when they need it. Will probably not start out with this, but it's a nice goal to have.

Initially depending on TCP will be the cheap way to go. The 'reliability option' for UDP would need to crystallize with time. You probably have some good suggestions for this. My current solution is just to be very verbose with updates ;)

Beyond that, i'm not clear on entity and component lifecycle management yet, like your death animation example. I'm certain we can go for a convention based solution that can be swapped out if needed, like the ability to have lifecycle controllers for individual entities in @junkdogs planned templating system?

And I already see that it's hard to automatically calculate delta for such specific Snapshot. Do you have any idea how to create non-specific system for delta calculation?

A very specific implementation! If determining delta is too hard to automate, I'd delegate delta determination to specialized controllers for people to customize. Maybe for high level we can generate a hash somehow? Or just snapshot serialized components and compare.

I'd say the server to client synchronization is where we'd gain the most with automation anyway. Client to server would be nice but probably only a small part of the typical network effort.

Just to be clear, are we talking the same delta? I'm assuming you mean send the values that changed since the last network update, and not have a delta relative to the earlier value.

Edit: So still in love with authoritive server / client, with high level automation delta pushes to clients, and clients manually sending events to the server. Do you see any issues with that as a middleground? I realize it will probably not suffice for situations where people need to get the most out of their networking.

DaanVanYperen commented 9 years ago

Crap you kept me up late! curses!

junkdog commented 9 years ago

Ok, time to start reading....

Btw, @DaanVanYperen , NS2d was mentioned in a religious thread for/against entity system frameworks over at jgo.

DaanVanYperen commented 9 years ago

Btw, @DaanVanYperen , NS2d was mentioned in a religious thread for/against entity system frameworks over at jgo.

Is this going to give me a bad mood before I go to bed? XD

junkdog commented 9 years ago

I don't know. NS2d was held in high regard though.

DaanVanYperen commented 9 years ago

You linked the same video twice. I'm sad now.

Edit: Found it Btw NS2D is not a great game to benchmark artemis, as I didn't spend any time optimizing it and it was slapped together in 72 hours. (but maybe that does prove a point about artemis' performance).

Edit2: Ugh, some people clearly enjoy debates like that more than I do. XD

junkdog commented 9 years ago

You linked the same video twice. I'm sad now.

I have other videos too, but I'm not sure what it says about the thread.

junkdog commented 9 years ago

Can you elaborate a bit where the nesting comes in? I'm assuming you're seeing a possibility to optimize.

Ah, nothing advanced yet - I've removed all static fields (except the static field generated by @PackedWeaver) from artemis, so it's possible to run several worlds simultaneously. As of now, it's only affects memory efficiency by not padding arrays with parent-World's system and component indices.

It doesn't have any means of sharing data between the worlds though; it has to be done manually.

Once there's a little more support for world-to-world communication (com.artemis.seti package?) - primarily linking entities between world and sharing components among worlds - it could be used to:

State delta, prediction, interpolation and such things are often needed not only for Health Points. So there is a need for some kind of general util. Maybe some "Network Property" which contains value, gain speed, loss speed, max gain, max loss, interpolation mode (easing - for hp bar animation?) and probably something more.

Interesting. This is per component type I assume? The only thing I'm missing (not that I have much of an idea, netowrking is a somewhat esoteric to me) is some sort of prioritization. though I'm not sure if that belongs on the entity instance level or component level - probably both, or atleast the former.

kryonet seems to be a nice library but I didn't want to create a strong dependency on top of it. I suggest not going this path. It's much easier to have such dependency instead of fighting with it.

+1, though I can mostly vouch for Nate's other libraries.

I'm not convinced about that same entities should be synced through artemis UUIDs. I've seen it in Unity and it sucked sometimes. Example: your unit died on server but you firstly want to show some die animation on client and disappear it only after that animated boom moment. Imagine that unit (entity) disappears during animation, well, it shouldn't do that. Semi-solution? Clone unit for boom animation... I don't like it. What do you think? IMHO It's better to kill entities manually.

I know Unity does things differently, but in artemis' case I'd set an Expire(delay) component and remove all unnecessary ones, and only delete the entity upon Expire.delay reaches 0. Someone has to be in charge of the killing though, but that is the same in any scenario.

I'll consider doing something like that for the next non Ludum Dare jam I participate in.

:)

In my experience it is. I think I ran into trouble with float deltas and had to force strict modes on all classes that dealt with with times so eventually I changed my utility classes like Timers to use integers but it is very likely that this isn't necessary if you make sure (and can afford) to enforce strict mode for determinism.

This whole float thing is a bit unfortunate... when I first came across artemis it was using int for delta. This would all go away if we could new World<int>() - but I'm not sure how to go about it. I can only come up with ugly hacks or lots of code duplication (leading to somehow generating a new artemis-odb artifact with int-world classifier - but it might break stuff all over the place and feels a bit unpleasant).

@junkdog Stealing your maven setup for artemis-odb-contrib! bwhoehaha. Now just need to figure out how to bribe someone for a spot on a maven repo. D:

This is actually rather easy: http://central.sonatype.org/pages/ossrh-guide.html - alternatively, you can use the net.onedaybeard.* groupId.

However, I'm still not sure what to do with those boolean deltas but I'm sure that position and orientation will be needed.

Why not pack them into a byte? They expand to one byte each - or does the system handle that?

I'm confident networking can be set up in such a way that it'll provide out of the box extra/interpolation and constraints on fields, and allow users to drop in specialized extra/interpolator classes when they need it. Will probably not start out with this, but it's a nice goal to have.

+1 (I'm running out of time, it's 4am here)

junkdog commented 9 years ago

In my experience it is. I think I ran into trouble with float deltas and had to force strict modes on all classes that dealt with with times so eventually I changed my utility classes like Timers to use integers but it is very likely that this isn't necessary if you make sure (and can afford) to enforce strict mode for determinism.

This whole float thing is a bit unfortunate... when I first came across artemis it was using int for delta. This would all go away if we could new World() - but I'm not sure how to go about it. I can only come up with ugly hacks or lots of code duplication (leading to somehow generating a new artemis-odb artifact with int-world classifier - but it might break stuff all over the place and feels a bit unpleasant).

Edit: ah, but if the simulation is running at a certain speed, one doesn't need floats for the simulation since is only takes steps.

Namek commented 9 years ago

FPS lag compensation in the sense of being able to rewind game state and determining where everything was goes a bit beyond my personal expertise, and will probably not be super critical for the types of games I personally make, though I certainly see how a data driven system is great to achieve historic snapshots.

Well, if you mean time rewind like in game "Braid" then no. Historic snapshots are needed only for sending smaller amount of data (deltas of gamestate) over the network.

Just to be clear, are we talking the same delta? I'm assuming you mean send the values that changed since the last network update, and not have a delta relative to the earlier value.

Yes.

However, I'm still not sure what to do with those boolean deltas but I'm sure that position and orientation will be needed.

Why not pack them into a byte? They expand to one byte each - or does the system handle that?

I do pack them through manual serializer. That was not problem for me. Actually, I started to have a dilemma how to process input and how to send it through network. Based on input server simulates game state so it's rather important for this input to be sent over the network properly. Using TCP may kill network performance. Otherwise I could implement some ACK-ing on UDP but I wonder how should it work when some packets aren't received by server. Example problem: player starts running (on key down) so I send info isRunning=true to the server. When I stop running (on key up) I send isRunning=false. What if some of those packets aren't delivered properly? No matter whether It's TCP or UDP with ACK-ing the information could be delivered late. But since it's ALWAYS late (but only a little)... then just some extrapolation and correction should be ok. I think I solved my problem just now.

Surely that example is specific to the game (shooters and racing games) but I think that making a ready solution for it would be a great prototyping performance gain. Unfortunately the day after tomorrow I go on a holiday so I won't be able to produce any results and do research on it for one and a half of week :(

Some more interesting links: http://www.boltengine.com/ - networking in Unity's GameObject Component model (similiar to ECS, but worse if you ask me). Maybe it will be some inspiration for you, Daan. http://www.mindcontrol.org/~hplus/epic/ - library for interpolation and extrapolation

DaanVanYperen commented 9 years ago

Well, if you mean time rewind like in game "Braid" then no. Historic snapshots are needed only for sending smaller amount of data (deltas of gamestate) over the network.

Ah figured you meant the Source method of lag compensation which is pretty involved.

Edit: Basically, server receives fire event from player 90ms after he shot, server rewinds the world 90ms to do a hitscan,

DaanVanYperen commented 9 years ago

spatially aware worlds: sharing systems but not components

My brain is too small to figure this one out! D: Halp! Does this originate from the 3D games are inefficient with artemis argument in that religious thread? ;)

networked worlds: tracking networked components, sending/receiving changesets. Might be less practical than I think it is, however.

Having this abstracted away from the newbie developer, this is something to strive for. This isn't nested though right?

Different worlds for different modes of gameplay: archetypical example would be an RPG, with distinct modes for walking around vs combat.

Having a nested world here would be awesome!

kryonet seems to be a nice library but I didn't want to create a strong dependency on top of it. I suggest not going this path. It's much easier to have such dependency instead of fighting with it.

Who said re-reading something isn't worth it. I totally misunderstood you the first time, thought you meant keep it framework agnostic instead of advocating a single framework. XD

So Kryonet it is! Happen to use it a long long time ago so it's probably still in there somewhere!

This is per component type I assume?

Not speaking for @Namek of course, but field granular would be the most flexible. At a high level, we let the developer decorate networking instructions at the component class, entity archetype, or entity, ideally on a per field basis. Hooking up a form of networking template to an entity via a manager seems to me like the most pragmatic solution, at least initially. Eventually we can expand this to templates and allow component and data type conventions.

Like @Namek mentioned, there is a place for interpolation/extrapolation and constraints outside just networked data. Any thoughts about this? I get the feeling it could be implemented separately from the networking, but no plans yet about how to go about this in a transparent and non invasive way. Sounds like we'll have to deal with component snapshots or shadows in some form or another. Both for a rewinding, delta checks, and to make *polation easier. Still have to read the links above ;)

If you have any good ideas how we can set up shadow components to contain the raw/polated values or entities with Artemis and exposing it to the developer in a sensible way i'm all ears. If I remember correctly Source has a method to rewind state and fast forward it after you're done prcessing.

Example problem: player starts running (on key down) so I send info isRunning=true to the server. When I stop running (on key up) I send isRunning=true. What if some of those packets aren't delivered properly?

I'd just be intermittently verbose if your bandwidth allows it, and assume the network is bad enough to require a resync ever so often. Send your player input regardless if things changed, possibly spaced by ticks you're comfortable with,

DaanVanYperen commented 9 years ago

@Namek nice links again! You're keepin' me readin'!

Namek commented 9 years ago

Just to note one fact - JavaScript doesn't support UDP. Only WebSocket is supported, which sucks because of HTTP-compliant header size. I'm talking about GWT.

DaanVanYperen commented 9 years ago

Just to note one fact - JavaScript doesn't support UDP. Only WebSocket is supported, which sucks because of HTTP-compliant header size. I'm talking about GWT.

Sounds like about as much fun as stapling my privates to my leg

Then again, if they can port quake to gwt, anything is possible! https://code.google.com/p/quake2-gwt-port/

junkdog commented 9 years ago

It only works in chrome though, but apparently not due to networking restrictions.

I wonder if network support for GWT could be pulled off in a clean way. Paying some respect to my aversion to javascript, maybe GWT support could be a secondary milestone - after a working solution for desktop and android has been conceived. Might be too much tackle at once otherwise.

DaanVanYperen commented 9 years ago

I wonder if network support for GWT could be pulled off in a clean way. Paying some respect to my aversion to javascript, maybe GWT support could be a secondary milestone - after a working solution for desktop and android has been conceived. Might be too much tackle at once otherwise.

Got to say for me, the artemis-gwt module is worth the pain you go through. ;)

Still, the prospect of having to go through that myself... stage 2 seems fine. XD

junkdog commented 9 years ago

Yeah, it's unfortunate that GWT support is such a big win for artemis - "roses are planted where thorns grow", I suppose.

DaanVanYperen commented 9 years ago

Question answered. This has been very helpful!

11mad11 commented 5 years ago

It's just a prototype and i don't know if it work. If it work and once cleaned it could be good as a simple exemple

package fr.mad.grimoire.server.atremis;

import java.util.Arrays;

import com.artemis.BaseSystem;
import com.artemis.Component;
import com.artemis.ComponentManager;
import com.artemis.ComponentType;
import com.artemis.ComponentTypeFactory.ComponentTypeListener;
import com.artemis.EntitySubscription;
import com.artemis.annotations.AspectDescriptor;
import com.artemis.utils.Bag;
import com.artemis.utils.BitVector;
import com.artemis.utils.IntBag;
import com.esotericsoftware.kryo.Kryo;

import fr.mad.componants.NetworkC;
import fr.mad.componants.NetworkI;
import fr.mad.shared.msg.EntityChanged;

public class NetworkSystem extends BaseSystem {

    @AspectDescriptor(all = NetworkC.class) EntitySubscription networkSub;
    private Kryo kryo;
    private ComponentManager cm;
    public BitVector coPass = new BitVector();
    @SuppressWarnings("unchecked") private Bag<Bag<Component>> snapshots = new Bag<Bag<Component>>((Class<Bag<Component>>) (Class<?>) Bag.class);

    private Bag<Component> components = new Bag<>(Component.class);
    private Bag<Component> changed = new Bag<>(Component.class);

    @Override
    protected void initialize() {
        cm.getTypeFactory().register(new ComponentTypeListener() {

            @Override
            public void onCreated(ComponentType type) {
                coPass.ensureCapacity(type.getIndex());
                coPass.set(type.getIndex(), NetworkI.class.isAssignableFrom(type.getType()));
            }

            @Override
            public void initialize(Bag<ComponentType> registered) {
                registered.forEach(this::onCreated);
            }
        });

        kryo = [[init and configure kryo]];
    }

    @Override
    protected void processSystem() {
        IntBag entities = networkSub.getEntities();
        int[] ids = entities.getData();
        int id;
        for (int i = 0, s = entities.size(); s > i; i++) {
            id = ids[i];
            changed.clear();
            components.clear();
            cm.getComponentsFor(id, components);
            for (int j = 0, s2 = components.size(); s2 > j; j++) {
                Component co = components.get(j);
                int coi = cm.getTypeFactory().getIndexFor(co.getClass());
                if (!coPass.get(coi))
                    continue;
                if (cp(id, coi, co))
                    changed.add(co);
            }
            sendChange(id, Arrays.copyOf(changed.getData(), changed.size()));
        }
    }

    private void sendChange(int enityId, Component[] changed) {
        EntityChanged change = new EntityChanged();
        change.id = enityId;
        change.added = changed;

        Bag<User> users = [[list of all user]];
        User[] datas = users.getData();
        for (int i = 0, s = users.size(); s > i; i++) {
            User data = datas[i];//TODO filter
            data.connection.sendTCP(changed);
        }
    }

    private boolean cp(int entityId, int cIndex, Component fresh) {
        Bag<Component> list = snapshots.safeGet(entityId);
        if (list == null)
            snapshots.unsafeSet(entityId, list = new Bag<Component>(Component.class));
        Component old = list.safeGet(cIndex);
        if (old == null || !old.equals(fresh)) {
            Component copy = kryo.copyShallow(fresh);
            list.unsafeSet(cIndex, copy);
            return true;
        }
        return false;
    }

}