aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config.go105
-rw-r--r--connect.go27
-rw-r--r--content.go64
-rw-r--r--doc/config.md14
-rw-r--r--doc/media_pools.md53
-rw-r--r--formspec.go2
-rw-r--r--process.go33
-rw-r--r--proxy.go2
-rw-r--r--server_conn.go4
9 files changed, 203 insertions, 101 deletions
diff --git a/config.go b/config.go
index a57ee4d..95e541d 100644
--- a/config.go
+++ b/config.go
@@ -2,6 +2,7 @@ package proxy
import (
"encoding/json"
+ "fmt"
"log"
"os"
"sync"
@@ -23,10 +24,12 @@ var configMu sync.RWMutex
var loadConfigOnce sync.Once
type Server struct {
- Name string
- Addr string
- TexturePool string
- Fallbacks []string
+ Name string
+ Addr string
+ MediaPool string
+ Fallbacks []string
+
+ dynamic bool
}
// A Config contains information from the configuration file
@@ -96,17 +99,17 @@ func UniquePoolServers() [][]Server {
// every server needs a texturePool property
for _, srv := range conf.Servers {
- if len(srv.TexturePool) == 0 {
- srv.TexturePool = srv.Name
+ if len(srv.MediaPool) == 0 {
+ srv.MediaPool = srv.Name
}
}
// map all to.. map of slices
for _, srv := range conf.Servers {
- if srvs[srv.TexturePool] != nil {
- srvs[srv.TexturePool] = append(srvs[srv.TexturePool], srv)
+ if srvs[srv.MediaPool] != nil {
+ srvs[srv.MediaPool] = append(srvs[srv.MediaPool], srv)
} else {
- srvs[srv.TexturePool] = []Server{srv}
+ srvs[srv.MediaPool] = []Server{srv}
}
}
@@ -121,40 +124,62 @@ func UniquePoolServers() [][]Server {
// AddServer dynamically configures a new Server at runtime.
// Servers added in this way are ephemeral and will be lost
// when the proxy shuts down.
-// For the media to work you have to specify an alternative
-// media source that is always available, even if the server
-// is offline.
-// WARNING: Reloading the configuration file deletes the
-// servers added using this function.
-func AddServer(server Server) bool {
+// The server must be part of a media pool with at least one
+// other member. At least one of the other members always
+// needs to be reachable.
+func AddServer(s Server) bool {
configMu.Lock()
defer configMu.Unlock()
+ s.dynamic = true
+
for _, srv := range config.Servers {
- if srv.Name == server.Name {
+ if srv.Name == s.Name {
return false
}
}
- config.Servers = append(config.Servers, server)
+ var poolMembers bool
+ for _, srv := range config.Servers {
+ if srv.MediaPool == s.MediaPool {
+ poolMembers = true
+ }
+ }
+
+ if !poolMembers {
+ return false
+ }
+
+ config.Servers = append(config.Servers, s)
return true
}
// RmServer deletes a Server from the Config at runtime.
-// Any server can be deleted this way, not just the ones
-// added using AddServer.
-// WARNING: Reloading the configuration files re-adds the
-// servers it contains that were deleted using this function.
-func RmServer(name string) {
+// Only servers added using AddServer can be deleted at runtime.
+// Returns true on success or if the server doesn't exist.
+func RmServer(name string) bool {
configMu.Lock()
defer configMu.Unlock()
for i, srv := range config.Servers {
if srv.Name == name {
+ if srv.dynamic {
+ return false
+ }
+
+ // Can't remove server if players are connected to it
+ for cc := range Clts() {
+ if cc.ServerName() == name {
+ return false
+ }
+ }
+
config.Servers = append(config.Servers[:i], config.Servers[1+i:]...)
- return
+ return true
}
}
+
+ return true
}
// FallbackServers returns a slice of server names that
@@ -225,6 +250,40 @@ func LoadConfig() error {
return err
}
+ // Dynamic servers shouldn't be deleted silently.
+DynLoop:
+ for _, srv := range oldConf.Servers {
+ if srv.dynamic {
+ config.Servers = append(config.Servers, srv)
+ } else {
+ for _, s := range config.Servers {
+ if srv.Name == s.Name {
+ continue DynLoop
+ }
+ }
+
+ for cc := range Clts() {
+ if cc.ServerName() == srv.Name {
+ config = oldConf
+ return fmt.Errorf("can't delete server %s with players", srv.Name)
+ }
+ }
+ }
+ }
+
+ for i, srv := range config.Servers {
+ for _, s := range config.Servers {
+ if srv.Name == s.Name {
+ config = oldConf
+ return fmt.Errorf("duplicate server %s", s.Name)
+ }
+ }
+
+ if srv.MediaPool == "" {
+ config.Servers[i].MediaPool = srv.Name
+ }
+ }
+
log.Print("load config")
return nil
}
diff --git a/connect.go b/connect.go
index 32b892c..746caa7 100644
--- a/connect.go
+++ b/connect.go
@@ -17,15 +17,10 @@ func connect(conn net.Conn, name string, cc *ClientConn) *ServerConn {
}
cc.mu.RUnlock()
- conf := Conf()
- var prefix string
- for _, srv := range conf.Servers {
+ var mediaPool string
+ for _, srv := range Conf().Servers {
if srv.Name == name {
- if len(srv.TexturePool) == 0 {
- prefix = srv.Name
- } else {
- prefix = srv.TexturePool
- }
+ mediaPool = srv.MediaPool
}
}
@@ -36,7 +31,7 @@ func connect(conn net.Conn, name string, cc *ClientConn) *ServerConn {
initCh: make(chan struct{}),
clt: cc,
name: name,
- prefix: prefix,
+ mediaPool: mediaPool,
aos: make(map[mt.AOID]struct{}),
particleSpawners: make(map[mt.ParticleSpawnerID]struct{}),
sounds: make(map[mt.SoundID]struct{}),
@@ -53,15 +48,15 @@ func connect(conn net.Conn, name string, cc *ClientConn) *ServerConn {
return sc
}
-func connectContent(conn net.Conn, name, userName, prefix string) (*contentConn, error) {
+func connectContent(conn net.Conn, name, userName, mediaPool string) (*contentConn, error) {
logPrefix := fmt.Sprintf("[content %s] ", name)
cc := &contentConn{
- Peer: mt.Connect(conn),
- logger: log.New(logWriter, logPrefix, log.LstdFlags|log.Lmsgprefix),
- doneCh: make(chan struct{}),
- name: name,
- userName: userName,
- prefix: prefix,
+ Peer: mt.Connect(conn),
+ logger: log.New(logWriter, logPrefix, log.LstdFlags|log.Lmsgprefix),
+ doneCh: make(chan struct{}),
+ name: name,
+ userName: userName,
+ mediaPool: mediaPool,
}
if err := cc.addDefaultTextures(); err != nil {
diff --git a/content.go b/content.go
index 2436da7..5593bcf 100644
--- a/content.go
+++ b/content.go
@@ -35,8 +35,6 @@ type contentConn struct {
logger *log.Logger
- prefix string
-
cstate clientState
cstateMu sync.RWMutex
name, userName string
@@ -47,6 +45,8 @@ type contentConn struct {
salt, srpA, a, srpK []byte
}
+ mediaPool string
+
itemDefs []mt.ItemDef
aliases []struct{ Alias, Orig string }
@@ -359,21 +359,21 @@ func muxItemDefs(conns []*contentConn) ([]mt.ItemDef, []struct{ Alias, Orig stri
def.Name = "hand"
}
- prepend(cc.prefix, &def.Name)
- prependTexture(cc.prefix, &def.InvImg)
- prependTexture(cc.prefix, &def.WieldImg)
- prepend(cc.prefix, &def.PlacePredict)
- prepend(cc.prefix, &def.PlaceSnd.Name)
- prepend(cc.prefix, &def.PlaceFailSnd.Name)
- prependTexture(cc.prefix, &def.Palette)
- prependTexture(cc.prefix, &def.InvOverlay)
- prependTexture(cc.prefix, &def.WieldOverlay)
+ prepend(cc.mediaPool, &def.Name)
+ prependTexture(cc.mediaPool, &def.InvImg)
+ prependTexture(cc.mediaPool, &def.WieldImg)
+ prepend(cc.mediaPool, &def.PlacePredict)
+ prepend(cc.mediaPool, &def.PlaceSnd.Name)
+ prepend(cc.mediaPool, &def.PlaceFailSnd.Name)
+ prependTexture(cc.mediaPool, &def.Palette)
+ prependTexture(cc.mediaPool, &def.InvOverlay)
+ prependTexture(cc.mediaPool, &def.WieldOverlay)
itemDefs = append(itemDefs, def)
}
for _, alias := range cc.aliases {
- prepend(cc.prefix, &alias.Alias)
- prepend(cc.prefix, &alias.Orig)
+ prepend(cc.mediaPool, &alias.Alias)
+ prepend(cc.mediaPool, &alias.Orig)
aliases = append(aliases, struct{ Alias, Orig string }{
Alias: alias.Alias,
@@ -431,25 +431,25 @@ func muxNodeDefs(conns []*contentConn) (nodeDefs []mt.NodeDef, p0Map param0Map,
}
def.Param0 = param0
- prepend(cc.prefix, &def.Name)
- prepend(cc.prefix, &def.Mesh)
+ prepend(cc.mediaPool, &def.Name)
+ prepend(cc.mediaPool, &def.Mesh)
for i := range def.Tiles {
- prependTexture(cc.prefix, &def.Tiles[i].Texture)
+ prependTexture(cc.mediaPool, &def.Tiles[i].Texture)
}
for i := range def.OverlayTiles {
- prependTexture(cc.prefix, &def.OverlayTiles[i].Texture)
+ prependTexture(cc.mediaPool, &def.OverlayTiles[i].Texture)
}
for i := range def.SpecialTiles {
- prependTexture(cc.prefix, &def.SpecialTiles[i].Texture)
+ prependTexture(cc.mediaPool, &def.SpecialTiles[i].Texture)
}
- prependTexture(cc.prefix, &def.Palette)
+ prependTexture(cc.mediaPool, &def.Palette)
for k, v := range def.ConnectTo {
def.ConnectTo[k] = p0Map[cc.name][v]
}
- prepend(cc.prefix, &def.FootstepSnd.Name)
- prepend(cc.prefix, &def.DiggingSnd.Name)
- prepend(cc.prefix, &def.DugSnd.Name)
- prepend(cc.prefix, &def.DigPredict)
+ prepend(cc.mediaPool, &def.FootstepSnd.Name)
+ prepend(cc.mediaPool, &def.DiggingSnd.Name)
+ prepend(cc.mediaPool, &def.DugSnd.Name)
+ prepend(cc.mediaPool, &def.DigPredict)
nodeDefs = append(nodeDefs, def)
param0++
@@ -468,7 +468,7 @@ func muxMedia(conns []*contentConn) []mediaFile {
for _, cc := range conns {
<-cc.done()
for _, f := range cc.media {
- prepend(cc.prefix, &f.name)
+ prepend(cc.mediaPool, &f.name)
media = append(media, f)
}
}
@@ -499,7 +499,7 @@ func muxContent(userName string) (itemDefs []mt.ItemDef, aliases []struct{ Alias
for _, pools := range UniquePoolServers() {
var addr *net.UDPAddr
found := false
-
+
for _, srv := range pools {
addr, err = net.ResolveUDPAddr("udp", srv.Addr)
if err != nil {
@@ -514,10 +514,10 @@ func muxContent(userName string) (itemDefs []mt.ItemDef, aliases []struct{ Alias
// get prefix of server
var prefix string
- if len(srv.TexturePool) == 0 {
+ if len(srv.MediaPool) == 0 {
prefix = srv.Name
} else {
- prefix = srv.TexturePool
+ prefix = srv.MediaPool
}
var cc *contentConn
@@ -613,7 +613,7 @@ func prependTexture(prep string, t *mt.Texture) {
func (sc *ServerConn) prependInv(inv mt.Inv) {
for k, l := range inv {
for i := range l.Stacks {
- prepend(sc.prefix, &inv[k].InvList.Stacks[i].Name)
+ prepend(sc.mediaPool, &inv[k].InvList.Stacks[i].Name)
}
}
}
@@ -622,28 +622,28 @@ func (sc *ServerConn) prependHUD(t mt.HUDType, cmdIface mt.ToCltCmd) {
pa := func(cmd *mt.ToCltAddHUD) {
switch t {
case mt.StatbarHUD:
- prepend(sc.prefix, &cmd.Text2)
+ prepend(sc.mediaPool, &cmd.Text2)
fallthrough
case mt.ImgHUD:
fallthrough
case mt.ImgWaypointHUD:
fallthrough
case mt.ImgWaypointHUD + 1:
- prepend(sc.prefix, &cmd.Text)
+ prepend(sc.mediaPool, &cmd.Text)
}
}
pc := func(cmd *mt.ToCltChangeHUD) {
switch t {
case mt.StatbarHUD:
- prepend(sc.prefix, &cmd.Text2)
+ prepend(sc.mediaPool, &cmd.Text2)
fallthrough
case mt.ImgHUD:
fallthrough
case mt.ImgWaypointHUD:
fallthrough
case mt.ImgWaypointHUD + 1:
- prepend(sc.prefix, &cmd.Text)
+ prepend(sc.mediaPool, &cmd.Text)
}
}
diff --git a/doc/config.md b/doc/config.md
index 76aae2b..4e90976 100644
--- a/doc/config.md
+++ b/doc/config.md
@@ -13,13 +13,11 @@ This is an example configuration file with two servers. Remember to install
"Servers": [
{
"Name": "default_server",
- "Addr": "minetest.local:30000",
- "TexturePool": "mt_game"
+ "Addr": "minetest.local:30000"
},
{
"Name": "some_other_server",
- "Addr": "minetest.local:30001",
- "TexturePool": "mt_game"
+ "Addr": "minetest.local:30001"
}
]
}
@@ -119,11 +117,13 @@ Default: ""
Description: The network address and port of an internal server.
```
-> `Server.TexturePool`
+> `Server.MediaPool`
```
Type: string
-Default: ""
-Description: The texture Pool the server will be mapped to default is server name.
+Default: Server.Name
+Description: The media pool this server will be part of.
+See [media_pools.md](https://github.com/HimbeerserverDE/mt-multiserver-proxy/blob/main/doc/media_pools.md)
+for more information.
```
> `Server.Fallback`
diff --git a/doc/media_pools.md b/doc/media_pools.md
new file mode 100644
index 0000000..d2bf68b
--- /dev/null
+++ b/doc/media_pools.md
@@ -0,0 +1,53 @@
+# Media Pools
+
+All servers must be part of a media pool. By default the name of the server
+is used.
+
+## Background
+When the proxy sends any content-related packets to the client,
+it prefixes any content names such as node names or media file names
+with the media pool of the current server and an underscore.
+The purpose of this is to allow servers to have different media
+with the same name and to avoid some other multiplexing issues.
+
+## When to use media pools?
+In general, custom media pools are not required.
+There are reasons to use them:
+- reducing memory and storage usage on the client
+- dynamically adding servers
+
+### Reducing RAM and disk usage
+The client has to store all media it receives in memory and in its cache.
+Minetest doesn't do this very efficiently: Identical files with different
+names will not share memory, a copy will be made. Even if they did share
+memory the references would still consume memory themselves but that would
+probably be negligable.
+
+This may not look like a big issue but it is. Many machines, especially
+phones, still only have 4 GB of RAM or even less. It's quite easy to
+exceed this limit even with lightweight or basic subgames. This will make
+devices that don't have enough memory unable to connect. The game will crash
+while downloading media.
+
+The unnecessarily redundant caching will fill the permanent storage with
+unneeded files too. This isn't as big of a problem as the cache isn't
+(or at least shouldn't) be required for the engine to work. However
+inexperienced players are going to wonder where their disk space is going.
+
+### Dynamic servers
+These are a whole other mess but all you need to know is that they won't work
+without media pools. The reason is that connected clients can't get the new
+content without reconnecting due to engine restrictions. Media pools are
+pushed to the client when it connects. This requires the first server of the
+media pool to be reachable. This means you can make a dummy server for the
+media and prevent players from connecting to it, or just use a hub server
+as the media master.
+
+## How to use media pools?
+Simply specify the name of the media pool you'd like the server to be part of
+in the MediaPool field of the server definition. All server you do this for
+will be part of the pool. Alternatively you can specify the name of another
+server if that server doesn't have a custom media pool set or if it's the same
+as its name. This will result in the servers being in a media pool that has
+the same name as that server. You can use it to your advantage when creating
+and naming dummy servers.
diff --git a/formspec.go b/formspec.go
index 169d2f8..3ffa7ac 100644
--- a/formspec.go
+++ b/formspec.go
@@ -13,7 +13,7 @@ func (sc *ServerConn) prependFormspec(fs *string) {
for i, sub := range subs {
if textureName.MatchString(sub) && !strings.Contains(sub, " ") {
- prepend(sc.prefix, &subs[i])
+ prepend(sc.mediaPool, &subs[i])
}
}
diff --git a/process.go b/process.go
index 6737af1..c78daa3 100644
--- a/process.go
+++ b/process.go
@@ -447,12 +447,7 @@ func (cc *ClientConn) process(pkt mt.Pkt) {
go func(done chan<- struct{}) {
result, isCmd := onChatMsg(cc, cmd)
if !isCmd {
- if srv == nil {
- cc.Log("->", "no server")
- return
- }
-
- srv.Send(pkt)
+ forward(pkt)
} else if result != "" {
cc.SendChatMsg(result)
}
@@ -635,7 +630,7 @@ func (sc *ServerConn) process(pkt mt.Pkt) {
handStack := mt.Stack{
Item: mt.Item{
- Name: sc.prefix + "_hand",
+ Name: sc.mediaPool + "_hand",
},
Count: 1,
}
@@ -753,7 +748,7 @@ func (sc *ServerConn) process(pkt mt.Pkt) {
break
}
- prepend(sc.prefix, &cmd.Filename)
+ prepend(sc.mediaPool, &cmd.Filename)
if cmd.ShouldCache {
cacheMedia(mediaFile{
name: cmd.Filename,
@@ -763,17 +758,17 @@ func (sc *ServerConn) process(pkt mt.Pkt) {
}
case *mt.ToCltSkyParams:
for i := range cmd.Textures {
- prependTexture(sc.prefix, &cmd.Textures[i])
+ prependTexture(sc.mediaPool, &cmd.Textures[i])
}
case *mt.ToCltSunParams:
- prependTexture(sc.prefix, &cmd.Texture)
- prependTexture(sc.prefix, &cmd.ToneMap)
- prependTexture(sc.prefix, &cmd.Rise)
+ prependTexture(sc.mediaPool, &cmd.Texture)
+ prependTexture(sc.mediaPool, &cmd.ToneMap)
+ prependTexture(sc.mediaPool, &cmd.Rise)
case *mt.ToCltMoonParams:
- prependTexture(sc.prefix, &cmd.Texture)
- prependTexture(sc.prefix, &cmd.ToneMap)
+ prependTexture(sc.mediaPool, &cmd.Texture)
+ prependTexture(sc.mediaPool, &cmd.ToneMap)
case *mt.ToCltSetHotbarParam:
- prependTexture(sc.prefix, &cmd.Img)
+ prependTexture(sc.mediaPool, &cmd.Img)
case *mt.ToCltUpdatePlayerList:
if !clt.playerListInit {
clt.playerListInit = true
@@ -791,7 +786,7 @@ func (sc *ServerConn) process(pkt mt.Pkt) {
}
}
case *mt.ToCltSpawnParticle:
- prependTexture(sc.prefix, &cmd.Texture)
+ prependTexture(sc.mediaPool, &cmd.Texture)
sc.globalParam0(&cmd.NodeParam0)
case *mt.ToCltBlkData:
for i := range cmd.Blk.Param0 {
@@ -810,14 +805,14 @@ func (sc *ServerConn) process(pkt mt.Pkt) {
case *mt.ToCltAddNode:
sc.globalParam0(&cmd.Node.Param0)
case *mt.ToCltAddParticleSpawner:
- prependTexture(sc.prefix, &cmd.Texture)
+ prependTexture(sc.mediaPool, &cmd.Texture)
sc.swapAOID(&cmd.AttachedAOID)
sc.globalParam0(&cmd.NodeParam0)
sc.particleSpawners[cmd.ID] = struct{}{}
case *mt.ToCltDelParticleSpawner:
delete(sc.particleSpawners, cmd.ID)
case *mt.ToCltPlaySound:
- prepend(sc.prefix, &cmd.Name)
+ prepend(sc.mediaPool, &cmd.Name)
sc.swapAOID(&cmd.SrcAOID)
if cmd.Loop {
sc.sounds[cmd.ID] = struct{}{}
@@ -842,7 +837,7 @@ func (sc *ServerConn) process(pkt mt.Pkt) {
sc.prependFormspec(&cmd.Formspec)
case *mt.ToCltMinimapModes:
for i := range cmd.Modes {
- prependTexture(sc.prefix, &cmd.Modes[i].Texture)
+ prependTexture(sc.mediaPool, &cmd.Modes[i].Texture)
}
case *mt.ToCltNodeMetasChanged:
for k := range cmd.Changed {
diff --git a/proxy.go b/proxy.go
index 1375e1d..3d2f69d 100644
--- a/proxy.go
+++ b/proxy.go
@@ -16,7 +16,7 @@ import (
const (
latestSerializeVer = 28
latestProtoVer = 39
- versionString = "5.5.0-dev-83a7b48bb"
+ versionString = "5.4.1"
maxPlayerNameLen = 20
bytesPerMediaBunch = 5000
)
diff --git a/server_conn.go b/server_conn.go
index 10ca868..ba229d1 100644
--- a/server_conn.go
+++ b/server_conn.go
@@ -17,8 +17,6 @@ type ServerConn struct {
clt *ClientConn
mu sync.RWMutex
- prefix string
-
logger *log.Logger
cstate clientState
@@ -31,6 +29,8 @@ type ServerConn struct {
salt, srpA, a, srpK []byte
}
+ mediaPool string
+
inv mt.Inv
detachedInvs []string