Bacon.js restart buffering when another EventStream fires - javascript

I'm trying to make a simple canvas-drawing app using Bacon.js. It should allow to draw a line by selecting start and end point using mouse clicks like this.
points = paper.asEventStream('click')
.map((e) -> x: e.pageX, y: e.pageY)
.bufferWithCount(2)
So far so good. Now it should allow to cancel starting point selection by clicking Esc.
cancel = doc.asEventStream('keypress')
.map((e) -> e.keyCode)
.filter((key) -> key is 27)
.map(true)
What is the baconian way of restarting buffering?
UPD: The implementation I've got so far looks awful.
points
.merge(cancel)
.buffer(undefined, (buffer) ->
values = buffer.values
if values.some((val) -> val is true) then values.length = 0
else if values.length is 2 then buffer.flush())

You can start the buffering from cancel:
var filteredPairs = cancel.startWith(true).flatMapLatest(function() {
return points.bufferWithCount(2)
})
See jsFiddle

Related

RxJS wait for 2 separate clicks events

I have a button and an image.
When I click the button, I want it to go to a "wait mode" of sorts.
Waiting for two separate clicks, that both return the x, y value of the mouse click events.
I got the mouse xy part no problem but at loss for what RxJS operator to use next
const elem = document.getElementById("MyImage");
const root = fromEvent(elem, "click");
const xy = root.pipe(map(evt => xyCartoPos(elem, evt)));
xy.subscribe(coords => console.log("xy:", coords));
function xyCartoPos(elem, e) {
const elemRect = elem.getBoundingClientRect();
return {
x: e.clientX - elemRect.x - elemRect.width / 2,
y: flipNum(e.clientY - elemRect.y - elemRect.height / 2)
};
}
You can use bufferCount to emit a fixed number of clicks at once (in one array).
const xy = root.pipe(
map(evt => xyCartoPos(elem, evt)),
bufferCount(2),
//take(1) // use take(1) if you only want to emit one pair of clicks and then complete
);
You could use scan to collect the events as an array, then use filter to verify the length of the array is 2:
const xy = root.pipe(
map(evt => xyCartoPos(elem, evt)),
scan((acc, evt) => {
acc.push(evt);
return acc;
}, []),
filter(events => events.length == 2),
);
This will cause only an array with the two mouse events, after two clicks, to be published to the subscriber.

Fast and smooth scrolling using google maps api possible?

i am working on a react application in which we use google maps. The goal that we want to achieve is to make the map's scrolling as fast and as smooth as possible, and ideally as close to jslancerteam's vector map's scrolling, which can be found here: https://jslancerteam.github.io/crystal-dashboard/#/maps/vector-map . By fast i mean skip some zoom levels per mousewheel scroll as can be seen on the vector map above.
I have tried many things i found online but the closest i got was by disabling the default scrollwheel option of the map and trying to make a custom function called on every mousewheel scroll captured by a listener.
I have tried two different approaches , one by adding an integer value z as increment to the zoom level of the map (i think we need a value between 2 and 3 levels for each scroll.). I wrote the second approach myself and the idea behind it is to give different multipliers to map's zoom level depending on the value of the current zoom level.( For example having faster zoomIn if you are at a low zoom level and slower if you are already pretty zoomed in. The opposite is true for the zoomOut.).
Although i think the second approach has some potential to become smooth, sadly i don't believe the google maps can achieve the same kind of smoothness and fast scrolling as in the vector maps example.
In google maps every layer(tiles) is going to be rendered (grey tiles) even if you try to increase the zoom level by a great value on every scroll because they are served on demand.
When scrolling the mousewheel gently , the results are close to what we would like. When scrolling the mousewheel very fast, the map tiles don't have time to get rendered
On the other hand, we believe that other libraries don't render said layers , and thus they get that smooth feeling.
Unfortunately the application is very dependent on the maps and has a lot of it's basic functionality built around them , so we would like to avoid migrating to another maps library if we are able to for now.
// Need to point out that in some other point in the app
// the scrollwheel map option is set to false and
// the max zoom level is set to 22
//
//1st Approach
//
// Having disabled the default scrollwheel i wanted to
// make the custom scroll follow the mouse
// so i get the coordinates of the mouse through a listener
// and then i set it as the center of the map on every scroll
maps.event.addListener(map, 'mousemove', function (event) {
displayCoordinates(event.latLng);
});
let displayCoordinates=function (pnt) {
var lat = pnt.lat();
lat = lat.toFixed(4);
var lng = pnt.lng();
lng = lng.toFixed(4);
mouselatlng =new LatLng(lat,lng)
}
// the listener used to capture the mousewheel scroll
maps.event.addDomListener(map.getDiv(), 'mousewheel', fx);
// the function called on every scroll
// depending if you zoomIn or zoomOut
// a z is added to the zoom level of the map to replicate
// the zoom functionality
let fx = function(e) {
e.preventDefault();
map.setCenter(mouselatlng); //setting the mouse coords as center
//
// increase by 3 levels on zoomIn : decrease by 3 (-3) on zoomOut
//
var z = e.wheelDelta > 0 || e.detail < 0 ? 3 : -3;
map.setZoom(Math.max(0, Math.min(22, map.getZoom() + z)));
setTimeout(()=>{
map.setZoom(map.getZoom + z);
},80)
return false;
};
//
//2nd Approach
//
maps.event.addListener(map, 'mousemove', function (event) {
displayCoordinates(event.latLng);
});
let displayCoordinates=function (pnt) {
var lat = pnt.lat();
lat = lat.toFixed(4);
var lng = pnt.lng();
lng = lng.toFixed(4);
mouselatlng =new LatLng(lat,lng)
}
maps.event.addDomListener(map.getDiv(), 'mousewheel', fx);
let fx = function(e) {
e.preventDefault();
map.setCenter(mouselatlng);
// Using different multipliers for some zoom levels
// the logic is not finished and the numbers used are not
// final. This was a test to see if the multipliers could
// make a difference.
if(map.getZoom >= 10){
var z = e.wheelDelta > 0 || e.detail < 0 ? 1.1 : 0.91;
}
else if ((map.getZoom >= 7)){
var z = e.wheelDelta > 0 || e.detail < 0 ? 1.2 : 0.7;
}else{
var z = e.wheelDelta > 0 || e.detail < 0 ? 1.4 : 0.6;
}
map.setZoom(Math.max(0, Math.min(22, map.getZoom() * z)));
return false;
};

How does mouse movementX work on drag events?

I am trying to set up dynamic draggable element which will move its anchor point according to the drag delta. However I cannot seem to get the delta using event.movementX or event.movementY.
My simple drag event code:
mainMenuDiv.addEventListener("drag", (event)=>{
console.log(event.movementX, event.movementY);
});
// The console print out is simply:
//>0 0
// This prints out a lot of times as i drag the mouse but doesn't change the properties.
I think i am missing something about the way the movement property works with drag events.
I couldn't get it to work either, so I just did the following:
var start = null, delta = null
el.addEventListener('dragstart', e => {
start = {x: e.clientX, y: e.clientY}
})
el.addEventListener('drag', e => {
delta = {x: e.clientX - start.x, y: e.clientY - start.y}
})
el.addEventListener('dragend', e => start = delta = null)
You can use e.nativeEvent.offsetX and e.nativeEvent.offsetY, too.
I don't know the browser support though.

Multiplayer game movement synchronization

I'm working on a multiplayer game and i'm having problem with synchronizing players.
When a player presses one of the move keys (W,A,S,D) then the client sends a packet about the pressed button, the server sets the velocity according to the pressed key and sends back to all nearby players the new velocity.
When the player release the key then the clients sends a packet, server sets the player velocity to 0,0 and sends the position and velocity to all nearby players.
So the problem is when i release the key, then most of the time the player jumps back.
How could i fix this?
i'm using socket.io.
Client side:
socket.on('positionEntity', function (data) {
console.log((data.x - entities[data.id].x)+" "+(data.y - entities[data.id].y));
entities[data.id].setPosition(data);
});
$(document).keyup(function(e) {
if (e.keyCode == 87) {
keys.W = false;
socket.emit("stopMove", {dir: 0, time: Date.now()});
}
if (e.keyCode == 65) {
keys.A = false;
socket.emit("stopMove", {dir: 1, time: Date.now()});
}
if (e.keyCode == 83) {
keys.S = false;
socket.emit("stopMove", {dir: 2, time: Date.now()});
}
if (e.keyCode == 68) {
keys.D = false;
socket.emit("stopMove", {dir: 3, time: Date.now()});
}
});
$(document).keydown(function(e) {
if (e.keyCode == 87 && !keys.W) {
keys.W = true;
socket.emit("startMove", {dir: 0, time: Date.now()});
}
if (e.keyCode == 65 && !keys.A) {
keys.A = true;
socket.emit("startMove", {dir: 1, time: Date.now()});
}
if (e.keyCode == 83 && !keys.S) {
keys.S = true;
socket.emit("startMove", {dir: 2, time: Date.now()});
}
if (e.keyCode == 68 && !keys.D) {
keys.D = true;
socket.emit("startMove", {dir: 3, time: Date.now()});
}
});
Server side:
socket.on('startMove', function(data) {
if (data.dir == 0) socket.player.setMotionY(-5);
if (data.dir == 1) socket.player.setMotionX(-5);
if (data.dir == 2) socket.player.setMotionY(5);
if (data.dir == 3) socket.player.setMotionX(5);
io.sockets.emit("positionEntity", socket.player.serializePosition());
});
socket.on('stopMove', function(dir) {
socket.player.setMotionX(0);
socket.player.setMotionY(0);
io.sockets.emit("positionEntity", socket.player.serializePosition());
});
This is a very complex task you are working on and something I've done as part of a pet project ;)
You're working on a client-server architecture game so the server is final authority on game logic and decisions. They way you are currently handling rendering will make sudden changes in velocity and direction apparent due to latency (as you have noticed!)
The trick is to buffer movement information of remote players so you always render player with a slight delay. I kept things primitive in my project and used only positional data, not acceleration or velocity. Example, when player A moves on his machine a command is not instantly sent to receive acknowledgement, he moves and on the next tick of my networking send loop (10 ticks per second) his position is fired to the server who updates all clients in the vicinity with this new position. These clients have a buffer for each "remote" player which stores each position for a time (100 milliseconds) before rendering the update. In this way the client is rendered with a slight delay but I can interpolate (smooth the transition of the sprite/model) between each positional co-ordinate to achieve smooth motion with the illusion of velocity and acceleration.
A BASIC interpolation function for client code. This system only queued two updates for each remote player at most, where index 0 in the update array was the older of two. So index 0 might be the remote player position 0ms and index 1 is the remote player position at 100ms.
interpolate: function() {
var timeDifference = new Date().getTime() - this.serverUpdates[1].time;
// Percentage of time passed since update was received (I use 100ms gaps)
var interPercent = (timeDifference) / 100;
// Latest updates (index 1 is the newest state)
var s1 = this.serverUpdates[0],
s2 = this.serverUpdates[1];
// Need to lerp between values provided in latest update and older one
var p = (new Vector3(s2.position)).subtract(new Vector3(s1.position));
p = p.timesScalar(interPercent);
// New position is the older lerped toward newer position where lerp
//percentage is the time passed
this.position = new Vector3(s1.position).add(p);
// Now update rotation in a smooth manner
var rotationDifference = (s2.rotation - s1.rotation);
if (rotationDifference && rotationDifference != 0) {
this.rotation = s1.rotation + (rotationDifference * interPercent);
}
},
In that code I was receiving updates that were approx 100ms apart, so at time 0 position is s1 and time 100ms s2 is the position. So if 50ms have passed since we received s2 then the entity is 50% between the two positions. This was fine for my need but may not work out in other types of games or might need tweaking.
These resources are an excellent start to explain networked games and dealing with latency, you'll be amazed at the difference implementing interpolation and extrapolation will have on your game smoothness in remote clients.
http://gafferongames.com/networking-for-game-programmers/what-every-programmer-needs-to-know-about-game-networking/
https://developer.valvesoftware.com/wiki/Latency_Compensating_Methods_in_Client/Server_In-game_Protocol_Design_and_Optimization <- REALLY GOOD!
https://developer.valvesoftware.com/wiki/Lag_compensation
Good luck :)

Understanding Reactive Extensions in JavaScript with code

I am new to Reactive Extensions and JavaScript. Can someone help me unscrew the following code? It's from Matthew Podwysocki's Introduction to the Reactive Extensions to JavaScript.
<html>
<head>
<title>Learning ReactiveExtensions</title>
<!--scripts-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.1/jquery.min.js"></script>
<script src="rx.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function() {
var mouseDragMe = $("#mouseDragMe").context;
var mouseMove = Rx.Observable.FromHtmlEvent(mouseDragMe, "mousemove");
var mouseUp = Rx.Observable.FromHtmlEvent(mouseDragMe, "mouseup");
var mouseDown = Rx.Observable.FromHtmlEvent(mouseDragMe, "mousedown");
var mouseMoves = mouseMove
.Skip(1)
.Zip(mouseMove, function(left, right) {
return { x1 : left.clientX,
y1 : left.clientY,
x2 : right.clientX,
y2 : right.clientY };
});
var mouseDrags = mouseDown.SelectMany(function(md) {
return mouseMoves.TakeUntil(mouseUp);
mouseDrags.Subscribe(function(mouseEvents) {
$("#results").html(
"Old (X: " + mouseEvents.x1 + " Y: " + mouseEvents.y1 + ") " +
"New (X: " + mouseEvents.x2 + " Y: " + mouseEvents.y2 + ")");
});
});
});
</script>
</head>
<body>
<div id="mouseDragMe" style="border:solid 1px red;">
i am a rx newbie
</div>
</body>
</html>
There are descriptions of this out there. I would recommend looking at this video about Writing your first Rx Application. The code's in C# but the concepts are exactly the same regardless of the programming language.
Essentially you want to understand 3 things
Conceptualizing of a series of events as a Sequence. Just like an
array is a sequence of data in space, events can be thought of as a
sequence of data in time (or in motion).
The operators (i.e. Skip, Zip, SelectMany and TakeUntil)
Subscription semantics
In this scenario we have 3 source sequences; mouseMove, mouseUp and mouseDown.
The mouseMove sequence will push a value of the mouse coordinates each time the mouse is moved. For example if the mouse started at the top left corner of the screen and moved diagonally down then straight across you may see values like {0,0}, {10,10}, {20,10} published on the sequence.
The values that mouseUp and mouseDown publish are not interesting, just the point in time that they are published is interesting.
The actual problem of "dragging" requires us to know when the mouse button is pressed and the delta of where the mouse was when it was pressed, and where it was when the button was released. The way we can get the delta of these positions is to take values of the final position and minus the values of the original position. Even better is if we can get all of the intermediate delta values so we can animate the movement. If we take our sequence above, to get a delta of movements we want to have the original sequence and an off by one sequence
Original { 0, 0}, {10,10}, {20,10}
offby1 {10,10}, {20,10}
This allows us to calculate the deltas to figure out the movement (not just the location).
Original { 0, 0}, {10,10}, {20,10}
offby1 {10,10}, {20,10}
delta {10,10}, {10, 0}
The way we can achieve this with Rx is first to use Skip(1) to skip one value. This creates our offby1 sequence. Next we want to combine values in pairs. The Zip function gives us this (more in my blog post about Combining sequences with Zip).
We could rewrite the code you have above
var mouseMoves = mouseMove
.Skip(1)
.Zip(mouseMove, function(left, right) {
return { x1 : left.clientX,
y1 : left.clientY,
x2 : right.clientX,
y2 : right.clientY };
});
To be
var offby1 = mouseMove.Skip(1);
var mouseMoves = offby1.Zip(mouseMove, function(left, right) {
return { x1 : left.clientX,
y1 : left.clientY,
x2 : right.clientX,
y2 : right.clientY };
});
Once we have the pairs, we need to apply the simple math of newValue-OldValue=delta.
This gives us our delta sequence which is effectively our Movement sequence.
var offby1 = mouseMove.Skip(1);
var mouseMoves = offby1.Zip(mouseMove, function(current, last) {
return { x : current.clientX-last.clientX,
y : current.clientY-last.clientY };
});
Now we only want to get values when the mouse is down. To do this we use the SelectMany operator. This says for each value from the source, get 0 or more values from this source. In our case each time a mouseDown event happens we want to get all the delta events (as mouseMoves).
var mouseDrags = mouseDown.SelectMany(function(md) { return mouseMoves;});
However we only want to keep receiving them until a corresponding mouseUp event happens. To do this we TakeUntil the mouseUp event produces a value.
var mouseDrags = mouseDown.SelectMany(function(md) {
return mouseMoves.TakeUntil(mouseUp);
});
Now that we have all this plumbing done, we usually apply those movements to the Position/Margin/offset of a UI element. It appears in the example, we just print the value though.

Categories

Resources