[Haskell-cafe] Observer pattern in haskell FRP
Nathan Hüsken
nathan.huesken at posteo.de
Mon Dec 10 16:28:37 CET 2012
On 12/08/2012 10:32 AM, Heinrich Apfelmus wrote:
> Nathan Hüsken wrote:
>> Heinrich Apfelmus wrote:
>>>
>>> In that light, the separation seems straightforward to me. Given the
>>> time-varying values that represent game objects,
>>>
>>> bSpaceShipPosition :: Behavior Position
>>> bAsteroidPositions :: Behavior [Position]
>>> bTime :: Behavior Time
>>>
>>> you can transform and combine them into a graphic, for instance like
>>> this
>>>
>>> bSpaceShipPicture :: Behavior Graphic
>>> bSpaceShipPicture =
>>> blinkenLights <$> bTime <*> bSpaceShipPosition
>>>
>>> bAsteroidPictures = map drawAsteroid <$> bAsteroidPositions
>>>
>>> bPicture = overlay <$>
>>> ((:) <$> bSpaceShipPicture <*> bAsteroidPictures)
>>>
>>> In other words, you just combine old time-varying values into new
>>> ones, much like you would combine combine graphical plots. Also note
>>> that you can add animation a posteriori; it doesn't have to be part
>>> of the values representing a space ship.
>>>
>> Yes, but these examples are extremely simple. The animation has no
>> internal "state". What if every Asteroid also has a animation state
>> (which I would want to add a posteriori) and can be destroyed.
>> Than the connection between the asteroids "game logic" value, and
>> "rendering" value needs some kind of bookkeeping to be maintained.
>
> Fair enough, but I don't see how this can be fitted into a general
> pattern. If the animation "state" is coupled tightly to the game logic
> "state", then the question whether the animation is part of the game
> logic or not does not have a clear answer anymore. Hm.
I see it like this: The "logic state" may not depend on the "rendering
state". If for example the animation of an asteroid changes how or when
objects collide with it, than the animation is part of the logic.
If however the physics of the game treat the asteroid as a object whose
shape does not change depending in the animation, than the animation is
part of the "rendering state".
So the coupling may only be logic->rendering.
> You mentioned that in an imperative setting, you would use something
> similar to the observer pattern. Judging from the wikipedia page
> <http://en.wikipedia.org/wiki/Observer_pattern>, it seems to me that
> this is just the Event type, though, so I don't understand how this
> helps with the problem at hand.
Yes, you are right. The problem at hand is something one has to deal
with when using the observer pattern. Only in C++ I do not find it hard
to do.
> Maybe discussing a concrete example could be very helpful. Could you
> give a minimal example that still contains the key difficulties? Maybe a
> collection of asteroids that float in space, can be added or removed
> with a button click and who are animated as rotating rocks, all starting
> in a certain position when they are created? How would you use the
> observer pattern in this case?
I put a pseudo C++ example below the mail. I use the terms "model" and
"view" for the game logic and rendering respectively.
The example is a little different. Asteroids explode when they collide.
The moment asteroids explode, they are removed from the model (the game
logic) while in the view (rendering) they still exist until the
explosion animation is over.
As you said, this basically is sending messages from the Model (in the
observer pattern called Observable) to the view (Observer). The main
difficulty I have is how to send the messages from the correct model to
the correct view.
In C++ this is done by keeping pointers.
Simply assigning IDs would work, but than I would have to always pass a
map from the model to the view, and I feel like (also I have little
experience with this), that this approach is not very scalable.
Best Regards,
Nathan
===========
The C++ example, its not complete, but I believe it captures the idea.
class GalaxyModel {
GalaxyView* view;
list<AsteroidModel> asteroids;
void addAsteroid(Position pos) {
asteroids.push(AsteroidModel(pos));
view->onNewAsteroid(asteroids.back());
}
void update() {
for (a in asteroids) {
a.update();
}
for(a in asteroids) {
for (b in asteroids) {
if (collide(a,b)) {
a.explode();
b.explode();
asteroids.remove(a);
asteroids.remove(b);
}
}
}
}
};
class AsteroidModel {
AsteroidView* view;
Position pos;
update() {
updatePosition();
view->onNewPos(pos);
}
explode() {
view->onExplode();
}
};
class GalaxyView {
list<AstroidView> asteroids;
void onNewAsteroid(AsteroidModel* model) {
asteroids.push(AsteroidView(model));
}
void update() {
for (a in asteroids) {
a.update();
if (a.dead) asteroids.remove(a);
}
}
}
class AsteroidView {
float rotation;
bool dead = false;
void onExplode() {
setExplodeAnimation();
}
void onNewPos() { //Store position}
void update() {
rotation += 1.0;
if (explodeAnimationOver()) {
m_dead = true;
}
}
}
More information about the Haskell-Cafe
mailing list