--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
package world
import "world/msg"
type World struct { // map, players and monsters...}
type (w *World) Begin() *Tx { w.mu.Lock()}
type Tx struct { w *World // msg contains messages collected while running the transaction. msg map[[]int][]msg.Msg // map[creature ids]messages}
func (tx *Tx) Send(to []int, m ...msg.Msg) { tx.msg[to] = append(tx.msg[to], m...)}
// AddCreature is an example of how a transaction can mutate the world state,// and it may be called by the server package or any other client of World. func (tx *Tx) AddCreature(name string, l Loc) int { // add creature to tx.w.map ... // Broadcast CreatureAppear to spectators. id := tx.nextCreatureID() tx.w.Send(tx.Spectators(l), msg.CreatureAppear{ ID: id, Name: name, Loc: l, }) return id}
// Spectators returns ID of any creature within range to see Loc.func (tx *Tx) Spectators(Loc) []int
func (tx *Tx) Close() { tx.w.mu.Unlock() // The world lock has been freed and the following would be // called from the goroutine that initiated this transaction. // This is the time to send messages collected from the transaction. for recipients, messages := range tx.msg { for _, id := range recipients { // tx.Creature returns a Creature interface assigned by the server package // or any other client of this World. If it is a player, it should // implement Send to transfer this message to it's client. If it's // a monster, it may implement logic to read those changes // and respond accordingly by initiating another transaction. tx.Creature(id).Send(messages...) } }}
package world
import "world/msg"
type World struct { // map, players and monsters...}
type (w *World) Begin() *Tx { w.mu.Lock()
return &Tx{w: w}
}
type Tx struct { w *World // msg contains messages collected while running the transaction. msg map[[]int][]msg.Msg // map[creature ids]messages}
// Send adds a message to be sent immediately after the transaction ends.
func (tx *Tx) Send(to []int, m ...msg.Msg) { tx.msg[to] = append(tx.msg[to], m...)}
// AddCreature is an example of how a transaction can mutate the world state,// and it may be called by the server package or any other client of World. func (tx *Tx) AddCreature(name string, l Loc) int {
id := tx.nextCreatureID()
// add creature to tx.w.map ... // Broadcast CreatureAppear to spectators.
tx.Send(tx.Spectators(l), msg.CreatureAppear{
import "world/msg"
type Server struct{}
func (s *Server) Start() error { // accept connections and call `go s.handleConnection(conn)` on each.}
func (s *Server) handleConnection(conn net.Conn) { // login ... // Register player to the world. var creatureID int rcv := worldMsgReceiver{conn: conn} // defined below s.world.Update(func(tx *world.Tx) { creatureID = tx.AddPlayer(name, health, ..., rcv) }) for { // znet is the networking package that speaks the game's binary protocol. i, err := znet.Receive(znet.Client, conn) if err != nil { break } switch pkt := i.(type) { // znet.NorthPacket is sent when the player wants to walk north. // It does not carry any data except it's own identity. case znet.NorthPacket: // world.World.Update locks the world for readwrite and executes // a transaction. // No other transactions can run before this one finished. s.world.Update(func(tx *world.Tx) { // Walk sends a msg.UpdateMap message if the walk // went well. Otherwise, it sends msg.CancelWalk. // Immediately after this transaction is executed, // and the world lock has been freed, either message // would be sent to our rcv instance and other receivers // of nearby creatures. tx.Player(creatureID).Walk(world.North) }) case znet.InventoryOpenPacket: // world.World.View locks the world for read and executes a // transaction. // Only other read transactions can run while this one is running. s.world.View(func(tx *world.Tx) { inv := tx.Player(creatureID).Inventory() err := znet.Send(conn, znet.InventoryPacket{ Bags: inv.Bags, Items: inv.Items, }) checkErr(err) }) } } conn.Close()}
type worldMsgReceiver struct{ conn net.Conn}func (r *worldMsgReceiver) Receive(creatureID int, m msg.Msg) { switch v := m.(type) { case msg.CreatureAppear: err := znet.Send(r.conn, znet.CreatureAppearPacket{ ID: v.ID, Name: v.Name, Look: v.Look, // ... }) checkErr(err) }}
On Saturday, March 26, 2016 at 11:42:46 AM UTC-3, Zippoxer wrote:
Your program very simple and clear :)
I have code using this strategy for some years now, with no problem at all. It works like a charm.
O JuciÊ
--
Hi,I'm building a server for a very tiny MMORPG.
- The world package is responsible for initializing and managing a world instance which holds the map, items, monsters and players in it.
The World struct holds the world's state and disallows concurrent read-write by restricting you to only view and update the state via transactions.Similarly to boltdb, you must utilize a transaction with World.View or World.Update in order to view/update the world's map, players or anything within the world's state.
Updates to anything should be sent to players within the world. If you change the map, that change should be sent to nearby players.
The world and the server package have a two way connection. The Server mutates the world according to players' commands, and the World passes messages to the server to send to relevant players on any change in the map, or anything within it's state.My problem is, how do I bridge between the world and the server? The world has no knowledge of how to do the networking and update players of changes itself, so how should it pass updates to the server to be sent for players?