For each client connection, I'd have a goroutine running which parses
the client-server protocol and sends the relevant messages via chans
to their destinations, and one goroutine that does the reverse,
reading from one or more chans and sending informations back over the
network. I would then have a separate goroutine for every logical
entity in your system, e.g. one per room and one per table. Each of
these sorts of goroutines will need to keep a map of entity names to
chans and whatever other information is needed (e.g. are they sitting
North or South). The beautiful thing is that all this data is local
to each goroutine, so there is no locking needed beyond what happens
internally in the chans.
I'm not sure if that is helpful advice. In general, where in C++ you
might have an object, in go I would have a goroutine--at least for
this sort of problem. Of course, that's not true for smallish objects
(e.g. cards), but for larger or more complicated objects, that's how
I'd think about making the transition.
David
--
David Roundy
Hope this helps, Roger.
2011/2/11 Henry Heikkinen <hopso...@gmail.com>:
Hmmm. How I would do that would depend on how the monsters or spawns
interact. In general, it's nice to have goroutines for things that
may interact asynchronously. So if your rpg is turn-based, or
monsters only respond to user input, then probably they don't deserve
goroutines. But if it's a real-time game so that monsters can act
whenever they like, and might respond to events that they perceive,
then I think a goroutine approach might be good.
Although goroutines are cheap, they aren't free. My experience was an
online bridge game, where *everything* is asynchronous except the game
logic, which is a small part of the code, which is why I made the
(over) generailization of objects mapping to goroutines. The primary
advantage of goroutines + chans for game elements is that it allows
you to put your abstractions into code without having to deal with
locks, etc. So if monsters only respond to specific events, then they
can listen on a chan for those events, and then interact by sending
events to other chans. On the other hand, if you have a real-time
timestep-based system, where every timestep each AI decides what to
do, then putting monsters into separate goroutines is probably way
more complex than you want...
David
i think it's probably best to make goroutines out of independent
pieces of code that have well defined relationships.
another way of doing it is to have the game objects communicate
with a central goroutine rather than directly to each other.
> A bit stray question: is it possible to implement built-in multi-server
> support?
> I mean - like erlang does - with proper setup spawning one more process may
> actually employ a completely different cpu residing on physically different
> computer, while all internal messaging (channels) continue working in
> exactly same way, transparently providing guaranteed message delivery (w/o
> order guaranties) over network.
Communication between threads in a shared memory space and between
threads which do not share memory is inherently different. Go has
chosen to not unify those two different ideas in a single mechanism.
Erlang does unify them, which is appropriate considering that Erlang was
originally designed not for multi-core microprocessors but for separate
processors with high speed network connections.
For communication between programs running on different processors, Go
provides the gob and rpc packages, which can be viewed as a modified
form of channels designed for cases where memory is not shared.
Ian
probably should mention netchan here too.
I guess it would depend on the sorts of independent objects that show
up. As long as they are interacting asynchronously (each object
reacts to events as they arrive, and immediately respond to those
events, sending events to objects as needed) it's hard to see how
you'd run into a deadlock.
> i think it's probably best to make goroutines out of independent
> pieces of code that have well defined relationships.
I'm not sure that I see how that differs from independent objects.
> another way of doing it is to have the game objects communicate
> with a central goroutine rather than directly to each other.
--
David Roundy
i disagree, although it depends how you set up your object-to-object
communication.
say each object had its own goroutine, doing something like:
func (obj *someObject) run() {
for {
e := <-obj.incoming
switch e.(type) {
case pickedUp:
obj.someonePickedMeUp(e)
etc ....
}
}
the someonePickedMeUp method might wish to send an
event to another object with a similar event loop.
the moment you make a cycle (an easy thing to do if you
haven't got a strictly hierarchical set of objects) then
you'll get deadlock.
>> i think it's probably best to make goroutines out of independent
>> pieces of code that have well defined relationships.
>
> I'm not sure that I see how that differs from independent objects.
i guess what i meant is that the pieces of code should have *statically*
well defined relationships, so you can build a program which is
deadlock-free by construction.
i haven't had much joy trying to come up with a framework
where objects are treated as "agents" and interact on an ad-hoc
basis with other objects. maybe there is a nice way of doing
it - if there is, i'd be very happy to see it.
Yeah, the key is in the asynchronous communication (which I referred
to, but wasn't explicit), which needs to be actually done
asynchronously. If you try to implement an asynchronous communication
using synchronous primitives, then yes, you will run into trouble.
They've just removed the non-blocking send from the language, so now
you'd want do create chans with buffers and then for each send you'd
do something like:
select {
case ch <- value:
default:
go func() { ch <- value }
}
and you've got asynchronous communication. As long as you use this
everywhere (and your code flow is designed to operate asynchronously,
e.g. you never wait for an event before doing something), deadlocks
should be a thing of the past. And rather than having a type switch,
I'd probably have separate channels for separate communications, so
you have just one select switch. But that's mostly just a matter of
taste and static type guarantees.
David
> say each object had its own goroutine, doing something like:
>
> func (obj *someObject) run() {
> for {
> e := <-obj.incoming
> switch e.(type) {
> case pickedUp:
> obj.someonePickedMeUp(e)
> etc ....
> }
> }
>
> the someonePickedMeUp method might wish to send an
> event to another object with a similar event loop.
>
> the moment you make a cycle (an easy thing to do if you
> haven't got a strictly hierarchical set of objects) then
> you'll get deadlock.
>
>>> i think it's probably best to make goroutines out of independent
>>> pieces of code that have well defined relationships.
>>
>> I'm not sure that I see how that differs from independent objects.
>
> i guess what i meant is that the pieces of code should have *statically*
> well defined relationships, so you can build a program which is
> deadlock-free by construction.
>
> i haven't had much joy trying to come up with a framework
> where objects are treated as "agents" and interact on an ad-hoc
> basis with other objects. maybe there is a nice way of doing
> it - if there is, i'd be very happy to see it.
>
--
David Roundy
that's true if all you do is send (and you don't care if events are arbitrarily
reordered).
but what if you actually want to acquire some data from one of these
objects? using a blocking receive will get you into exactly the
same deep water - deadlocks. and using a non-blocking receive isn't
great because then you're likely to miss data even when it's
available.
there's no silver bullet for deadlocks, and
in my experience arbitrary peer-to-peer communications
of this nature make it very easy for the wolf to appear...
> And rather than having a type switch,
> I'd probably have separate channels for separate communications, so
> you have just one select switch. But that's mostly just a matter of
> taste and static type guarantees.
yes, both approaches can be appropriate in different domains.
They've just removed the non-blocking send from the language, so now
you'd want do create chans with buffers and then for each send you'd
do something like:select {
case ch <- value:
default:
go func() { ch <- value }
}