Add socket.io module
Add a module to integrate socket.io.
Potential benefits:
- sharing/collaboration, several users seeing the same scene remotely, contributors able to modify elements in the scene.
- multi-player gaming, whs components attributes are kept in sync across clients, customer components containing the app state also sent off to other users through the server.
The module would encapsulate socket.io, and add capabilities to components:
- emitting modifiers. e.g set position. scale, rotation (sending off params to a server)
- receivers to update its attributes.
Proof of concept module - Checklist
- [x] Simple socket.io server to receive receive/emit messages
- [x] WHS Module to handle the socket connection.
- [x] Pass around simple mesh creation and sync all clients with position.
- [x] Add state and polling on the client
- [ ] Investigate leveraging existing implementation, e.g pomelo, colyseus
- [ ] Handle state (physics?) on the server as authority.
- [ ] server side pooling
- [ ] Socket robustness, we should have one connection per client instance.
Version:
- [x] v2.x.x
- [ ] v1.x.x
Issue type:
- [ ] Bug
- [x] Proposal/Enhancement
- [ ] Question
Tested on:
--- Desktop
- [ ] Chrome
- [ ] Chrome Canary
- [ ] Chrome dev-channel
- [ ] Firefox
- [ ] Opera
- [ ] Microsoft IE
- [ ] Microsoft Edge
--- Android
- [ ] Chrome
- [ ] Firefox
- [ ] Opera
--- IOS
- [ ] Chrome
- [ ] Firefox
- [ ] Opera
For sharing a scene --If you have good serialization / deserialization for the data model, something like ShareJS works well. Socket's probably better for gaming, though.
thanks @dmtaub, we are experimental stage, we will explore what fits best :) We might have different modules
- sync scene to any client, ShareJS sounds like a good approach
- game networking, physics, etc. socket.io
I've been wanting to do this, I have a serious amount of experience in Socket.IO, and I really have wanted to build such a module since I saw this project, I will work on it, if you don't mind, of course.
NOTE: This is all just me thinking about this, so don't take this super seriously.
Also, I think I have a different solution that still works great, and is a module, but it has more of a twist, I don't think that the default approach that first comes to mind is the best.
Above, hirako said this in his TODOs: "Handle state (physics?) on the server as authority."... The way I'm thinking of would allow that to definitely not be a problem, because remember, Unity3D ran into the same problem, right? I think there could be more of a Unity3D Type of networking scheme, and If I took it further, I would say that we could use a similar component/modular mixed system with Sockets. We will use components solely for sockets, if this idea were to work... Examples of use are always how I start concepts, so it's easier to conceptualize exactly what you are trying to achieve so I made a small little version of the HelloWorld example showing how simple one could make the use of this.
Here is the example of how I believe that this should be used. This would allow for proper socket.io physics simulations, without going insane trying to create individual methods for all of the server/client side methods:
import * as UTILS from '../../globals';
const app = new WHS.App([
new WHS.ElementModule(),
new WHS.SceneModule(),
new WHS.NetworkModule(UTILS.localhostNetwork.host,
UTILS.localhostNetwork.port,
UTILS.localhostNetwork.scheme),
new WHS.DefineModule('camera', new WHS.PerspectiveCamera(UTILS.appDefaults.camera)),
new WHS.RenderingModule(UTILS.appDefaults.rendering, {
shadow: true
}),
new PHYSICS.WorldModule(UTILS.appDefaults.physics),
new WHS.OrbitControlsModule(),
new WHS.ResizeModule(),
new StatsModule()
]);
const sphere = new WHS.Sphere({ // Create sphere comonent.
geometry: {
radius: 5,
widthSegments: 32,
heightSegments: 32
},
modules: [
new PHYSICS.SphereModule({
mass: 10,
restitution: 1
}),
new WHS.NetworkIdentity({
serverAuthority: false,
localAuthority: true
}),
new WHS.NetworkTransform({
movement: {
x: true,
y: true,
z: true
},
rotation: {
x: true,
y: true,
z: true
},
interpolation: 1 // The lerping of the movement, etc...
});
],
material: new THREE.MeshPhongMaterial({
color: UTILS.$colors.mesh
}),
position: new THREE.Vector3(0, 20, 0)
});
app.NetworkModule.commands(function(e) { // Not sure what to call this... Commands, RPCs, Whatever you want.
switch(e) {
case 'customEvent':
sphere.material.color = 0x444444;
// DO STUFF
}
});
sphere.addTo(app);
UTILS.addBoxPlane(app);
UTILS.addBasicLights(app);
app.start(); // Start animations and physics simulation.
Sure, feel free to take over. We parked this to focus on less ambitious modules. We had a prototype working, the next step was to figure out the right poll algorithm to send/receive updates. Update on all change straight away would not work.
Right, that is what I've been saying, the Unity3D approach seems to be nice because objects in the scene without the networkTransform will not update in the networking scheme. So, in WhitestormJS, Objects in the scene that are static will simply have a NetworkIdentity, and objects that you want to update will have the NetworkTransform
What do you think?
Sounds right. I was just making sure to point out I didn't write any polling algorithm (or reused one). Let's start with something simple. and expand on it. And totally yes taking inspiration from what Unity/Unreal engines are doing is an excellent idea. let's not reinvent the wheel.
Another example would be this piece of code in your module code:
let material = null;
switch (msg.mesh.material.type) {
case 'MeshBasicMaterial':
material = new THREE.MeshBasicMaterial();
break;
case 'MeshPhongMaterial':
material = new THREE.MeshPhongMaterial();
break;
case 'MeshLambertMaterial':
material = new THREE.MeshLambertMaterial();
break;
default:
material = null;
break;
That code should simply communicate the details of that to the client, and make sure that the client doesn't try to pass some dumbass value that's not a material.
So rather, It probably should be this (or something along the lines of this):
if(msg.mesh.material instanceof Material) {// I know none of this exists, but it's something to consider
material = material;
}
Does that sound right to you?
First things first though, we need to re-create the branch because it's 581 commits behind dev
sure, it was very prototypical. just to prove it would work. little knowledge of socket so I was just experimenting. yes create a different branch, start from scratch is neater anyway.
Okay! I'm on it, hirako! I love this project!
@hirako2000
By the way, socket happens to have a super useful event thing, it's this:
socket.on("*", callback);
That code will call the callback on EVERY event, super useful for building custom RPCs. That is how I'm working on my library, switch.io. So, I don't think we really need a full polling algorithm, we'll check the size of the movement, just like Unity3D does, so that way we can even set it to be an alter-able variable in the module's parameters.
On a sidenote: https://github.com/hden/socketio-wildcard
@TheCodeCrafter How about also supporting WebRTC technology in future? /ping @hirako2000
TOTALLY! Sounds awesome
I think that would be great for a database that maintain's itself once the server is running? What exactly are you planning on doing with WebRTC? @sasha240100
@TheCodeCrafter Could be transfering data between smartphone and PC (I saw some examples, where smartphone can be used as controller)
@sasha240100 That makes sense, but if you include that then you are probably going to have to go full on Unity3D and make axises for input, that way you can have a variety of controllers. XBox, PS4, Smartphone... I mean, if you want to go full on first party modules for almost everything, it would make sense to do that, right?
P.S. Also, I would also add automatic voip support to the networking if we did that?
Also, @sasha240100 I may need your help some more with the network module. What I want to do is make it so that the network module listens for all calls in the scene (any changes in the WHS world), and notify the server. How could I make such an alteration to the scene module, etc?
@TheCodeCrafter Let's use minivents system. You can wrap existing functions that should trigger changes and add the emit() there. Example:
const prevAdd = Component.prototype.add;
Component.prototype.add = function (parent) {
prevAdd.apply(this, parent);
parent.emit('change');
};
Another idea is to use bridge API whenever it is possible. For addTo such is possible though:
class AddToEventModule {
bridge = {
onAdd() {
this.emit('change');
}
}
}
const app = new WHS.App([
// ...
new AddToEventModule()
]);
app.on('change', () => {
console.log('scene changed');
});
Ok, since you have a much greater understanding of those systems, can you go ahead and implement them in the systems I have set in place, so that I can understand them better?
@TheCodeCrafter Yep, no problem, which events do you need to trigger change? (like add/remove)
I have re-read and updated myself on all of your suggestions and the current programming. I think I will try to use the minievents system, and I may need you to explain the bridging a bit more...
The events that we need to detect are all events that can change the scene visually.... If any changes are made to a model, or to a material, or to a position/rotation, or if an object is created/destroyed. Basically, all events that relate directly to altering the scene.
@TheCodeCrafter , one thing to help I think, is to try to identify a use case to drive the implementation. I see two major (and different) use cases. Satisfying both in one go might be difficult, pick one. Then we can think about making changes to make it accomplish the other case:
-
Networking for games. We can imagine a character control, that's the primary if not only input/emitting things the user would perform. The server then controls the effect on the rest of the scene (e.g pushing an object, shooting an enemy) and emits the changes to all clients.
-
Networking for collaboration. We can imagine a user has controls to nearly all object in the scene, can input/emit for nearly all objects (unless static, but even then, why would something be static). The server then controls nothing, it simply takes all sort of emissions (perhaps a set of difsf of to JSONed objects) and emit those changes to all clients. In this use case we could imagine users would first emit a 'lock' on the object they modify to avoid conflicts, but it is a nice to have, the core functions would just receive from all and emit to all.
@hirako2000 Then we should have 2 different modes. One for the collab mode and the gaming mode? I think we should focus on the gaming one anyways, and get it working before we try and flesh out all other uses... So I agree, let's start with the gaming one anyways. If you read my previous comment, you can identify which events I need to hook from the scene, to allow notification of the server, then we can get started and test it out... That's the one thing it's missing. It doesn't notify the server. Not once does it socket.emit...
If that's for the gaming case, then I foresee the input is what we need to send. thus:
-
Only input controls would emit from the client (the server is in charge to compute the effect of such input). Keeping it simple would be a left, right, up, down controller. If say such controller is the keyboard inputs, this controller would be a component,
KeyboardInputComponentthat component would implement the emit calls on key touches. -
The server would receive the emissions. Calculates the impact of those actions to the scene. And emit changes of everything in the scene.
Thus clients need to receive events from the server about impacted elements, and update all non-static elements in the scene. I believe components should be marked as non static when created if they are impacted by inputs of users. One could pass a flag to say a Sphere component, to make it non-static.
Each client would parse all elements in the scene, and for those that are non-static apply the update to them. E.g a Sphere can have its size and position changed if say the user bumps to it (to keep it simple). The emit from the server would be something like:
{
uuid: 1221321421321, // to make the scene know what to update
componentType: sphere, // just for debugging purpose really
position: {x = 1, y = 24, z = 20},
radius: 12
},
{
others...
}
All clients get the see the sphere moving as a user bumps into it. (Again the server is in charge of the logic, i.e how the sphere should move, we can't/shouldn't make it client side).
Edit: That's my view on things, please ignore if you have something else in mind that fits better or if you made enough progress to not want to revisit.
@hirako2000 You are always welcome to make suggestions, and never to be ignored in the case of anything I'm ever working on. Anyways, I did want to implement it that way, but I thought we were going for more of a Unity like approach. In Unity, changes in the scene on an object with a NetworkTransform Object would be displayed, allowing for easy physics calculations, we wouldn't want the server to have to handle the physics of everyone, that would be too much for the small node environments that developers use...
Therefore, if we detect changes in a scene on any object with a NetworkTransform module on it, we will emit the change, and the the change would be distributed to the other clients, and then individually implemented and physics simulated on each client, which will produce similar outcomes. There's no point in trying to get the server to simulate it.
Unless you are suggesting something different...
const app = new WHS.App([
new WHS.app.NetworkModule("localhost", "80", "http"),
new WHS.app.ElementModule(),
new WHS.app.SceneModule(),
new WHS.app.CameraModule({
position: new THREE.Vector3(0, 0, 50)
}),
new WHS.app.RenderingModule({bgColor: 0x162129}),
new WHS.app.ResizeModule()
]);
let sphere = new WHS.Sphere({
geometry: {
radius: 3,
widthSegments: 32,
heightSegments: 32
},
material: new THREE.MeshBasicMaterial({
color: 0xF2F2F2
}),
position: [0, 10, 0]
modules: [
new WHS.Networking.NetworkIdentity(), // To be accessible by the networking
new WHS.Networking.NetworkTransform() // To not be static
]
});
That is what I proposed, versus what you are saying
const app = new WHS.App([
new WHS.app.NetworkModule("localhost", "80", "http"),
new WHS.app.BasicKeyboadInput(),
new WHS.app.ElementModule(),
new WHS.app.SceneModule(),
new WHS.app.CameraModule({
position: new THREE.Vector3(0, 0, 50)
}),
new WHS.app.RenderingModule({bgColor: 0x162129}),
new WHS.app.ResizeModule()
]);
let sphere = new WHS.Sphere({
geometry: {
radius: 3,
widthSegments: 32,
heightSegments: 32
},
material: new THREE.MeshBasicMaterial({
color: 0xF2F2F2
}),
position: [0, 10, 0]
});
app.controls.networking(true); // This is simpler on the client side, but required more custom programming on the server side, which is something we considerable want to avoid..
So what I'm saying is, in Unity, they try to make most of the programming as client sided as possible, while if we did that where the server made calculations and other things, the person would interact much more with learning to properly use socket.io and node.js rather than being able to focus on WHS.js, with as little possible reliance on knowledge of such things.
I totally agree handling on the client would make the developer's focus remain on the client side, with a minimal server. My thoughts were based on other writings on the subject. e.g this:
Usually a server is a dedicated host that runs the game and is authoritative about world simulation, game rules, and player input processing. Source.
That being said, if Unity and the likes don't follow that rule of having the server as absolute authority, it means alternative approaches are proven to work. So let's do whatever is simplest or would make more sense for whs users.
Well, what we can do, is we can let the user define rules for their server, so the server will not allow certain packets to pass through, i.e. teleportation by moving to fast, etc. So we could allow for rules to be set on the server side?
You see, while most writings do say such a thing, that is for a more dedicated environment. Well, actually, their just the ones that are super worried about cheat protection, basically. And that's more of something that would take longer to make using an engine (WHS) that is meant for the client side.
Plus maybe we could add two different modes for the module later on, but atm, the easiest thing for us and the client end, would be to start with the server as a relay station, checking for malicious packets. Then maybe later on we can try and rig it for server side calculation.