From 3f31751d69ee114076e055468fc42cba3e420a85 Mon Sep 17 00:00:00 2001 From: Kevin Chabowski Date: Sun, 18 Aug 2013 12:33:55 +0200 Subject: Restructured code. Most non-GUI code was moved out of mapwidget.go --- emptyPixmap.go | 13 ++ main.go | 4 +- mapwidget.go | 549 ++++++++++++------------------------------------------ region_wrapper.go | 363 ++++++++++++++++++++++++++++++++++++ 4 files changed, 496 insertions(+), 433 deletions(-) create mode 100644 emptyPixmap.go create mode 100644 region_wrapper.go diff --git a/emptyPixmap.go b/emptyPixmap.go new file mode 100644 index 0000000..7635225 --- /dev/null +++ b/emptyPixmap.go @@ -0,0 +1,13 @@ +package main + +import ( + "github.com/mattn/go-gtk/gdk" +) + +// emptyPixmap creates an empty pixmap. +func emptyPixmap(w, h, depth int) *gdk.Pixmap { + // The underlying C function would create an empty, unbound pixmap, if the drawable paramater was a NULL pointer. + // Since simply passing a nil value would result in a panic (dereferencing a nil pointer), we use a new gdk.Drawable. + // gdk.Drawable contains a C pointer which is NULL by default. So passing a new(gdk.Drawable) does the trick. + return gdk.NewPixmap(new(gdk.Drawable), w, h, depth) +} diff --git a/main.go b/main.go index 4a7481d..44c72fd 100644 --- a/main.go +++ b/main.go @@ -38,7 +38,7 @@ func (g *GUI) openWorld(path string) { dlg.Destroy() } - go g.mapw.setRegion(region) + go g.mapw.SetRegion(region) } func (g *GUI) aboutDlg() { @@ -209,7 +209,7 @@ func (g *GUI) Init() { hbox := gtk.NewHBox(false, 0) - g.mapw = NewMapWidget(g.reportError, g.updateInfo, g.setBusy) + g.mapw = NewMapWidget(GUICallbacks{g.reportError, g.updateInfo, g.setBusy}) hbox.PackStart(g.mapw.DArea(), true, true, 3) sidebar := g.mkSidebar() diff --git a/mapwidget.go b/mapwidget.go index 0727038..e9cd97c 100644 --- a/mapwidget.go +++ b/mapwidget.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "github.com/kch42/gomcmap/mcmap" "github.com/mattn/go-gtk/gdk" "github.com/mattn/go-gtk/glib" @@ -11,70 +10,25 @@ import ( ) const ( - zoom = 2 - tileSize = zoom * mcmap.ChunkSizeXZ - cacheSize = 4 + zoom = 2 + tileSize = zoom * mcmap.ChunkSizeXZ ) -type tileCmd int - -const ( - cmdUpdateTiles tileCmd = iota - cmdFlushTiles - cmdSave -) - -func renderTile(chunk *mcmap.Chunk) (maptile, biotile *gdk.Pixmap, biocache []mcmap.Biome) { - maptile = emptyPixmap(tileSize, tileSize, 24) - mtDrawable := maptile.GetDrawable() - mtGC := gdk.NewGC(mtDrawable) - - biotile = emptyPixmap(tileSize, tileSize, 24) - btDrawable := biotile.GetDrawable() - btGC := gdk.NewGC(btDrawable) - - biocache = make([]mcmap.Biome, mcmap.ChunkRectXZ) - - i := 0 - for z := 0; z < mcmap.ChunkSizeXZ; z++ { - scanX: - for x := 0; x < mcmap.ChunkSizeXZ; x++ { - bio := chunk.Biome(x, z) - btGC.SetRgbFgColor(bioColors[bio]) - btDrawable.DrawRectangle(btGC, true, x*zoom, z*zoom, zoom, zoom) - - biocache[i] = bio - i++ - - for y := chunk.Height(x, z); y >= 0; y-- { - if col, ok := blockColors[chunk.Block(x, y, z).ID]; ok { - mtGC.SetRgbFgColor(col) - mtDrawable.DrawRectangle(mtGC, true, x*zoom, z*zoom, zoom, zoom) - continue scanX - } - } - - mtGC.SetRgbFgColor(gdk.NewColor("#ffffff")) - mtDrawable.DrawRectangle(mtGC, true, x*zoom, z*zoom, zoom, zoom) - } - } - - return +type GUICallbacks struct { + reportFail func(msg string) + updateInfo func(x, z int, bio mcmap.Biome) + setBusy func(bool) } type MapWidget struct { dArea *gtk.DrawingArea w, h int - reportFail func(msg string) - updateInfo func(x, z int, bio mcmap.Biome) - setBusy func(bool) - - toolsEnabled bool + guicbs GUICallbacks isInit bool - showBiomes, fixSnowIce bool + showBiomes bool offX, offZ int mx1, mx2, my1, my2 int @@ -86,234 +40,35 @@ type MapWidget struct { bg *gdk.Pixmap - region *CachedRegion - - maptiles map[XZPos]*gdk.Pixmap - biotiles map[XZPos]*gdk.Pixmap - bioCache map[XZPos][]mcmap.Biome + regWrap *RegionWrapper - redraw chan bool - tileCmds chan tileCmd - - tool Tool - bio mcmap.Biome + redraw chan bool } -var ( - checker1 = gdk.NewColor("#222222") - checker2 = gdk.NewColor("#444444") -) +func (mw *MapWidget) calcChunkRect() { -func emptyPixmap(w, h, depth int) *gdk.Pixmap { - return gdk.NewPixmap(new(gdk.Drawable), w, h, depth) -} - -func (mw *MapWidget) SetShowBiomes(b bool) { - mw.showBiomes = b - mw.redraw <- true -} - -func (mw *MapWidget) SetFixSnowIce(b bool) { - mw.fixSnowIce = b -} - -func (mw *MapWidget) SetTool(t Tool) { - mw.tool = t -} - -func (mw *MapWidget) SetBiome(bio mcmap.Biome) { - mw.bio = bio } func (mw *MapWidget) DArea() *gtk.DrawingArea { return mw.dArea } -func (mw *MapWidget) doTileCmds() { - for cmd := range mw.tileCmds { - switch cmd { - case cmdSave: - if err := mw.region.Flush(); err != nil { - mw.reportFail(fmt.Sprintf("Error while flushing cache: %s", err)) - return - } - if err := mw.region.Region.Save(); err != nil { - mw.reportFail(fmt.Sprintf("Error while saving: %s", err)) - return - } - case cmdFlushTiles: - if err := mw.region.Flush(); err != nil { - mw.reportFail(fmt.Sprintf("Error while flushing cache: %s", err)) - return - } - - gdk.ThreadsEnter() - for _, mt := range mw.maptiles { - mt.Unref() - } - for _, bt := range mw.biotiles { - bt.Unref() - } - gdk.ThreadsLeave() - - mw.maptiles = make(map[XZPos]*gdk.Pixmap) - mw.biotiles = make(map[XZPos]*gdk.Pixmap) - mw.bioCache = make(map[XZPos][]mcmap.Biome) - case cmdUpdateTiles: - todelete := make(map[XZPos]bool) - - startX := int(math.Floor(float64(mw.offX) / tileSize)) - startZ := int(math.Floor(float64(mw.offZ) / tileSize)) - endX := int(math.Ceil(float64(mw.offX+mw.w) / tileSize)) - endZ := int(math.Ceil(float64(mw.offZ+mw.h) / tileSize)) - - for pos := range mw.maptiles { - if (pos.X < startX) || (pos.Z < startZ) || (pos.X >= endX) || (pos.Z >= endZ) { - todelete[pos] = true - } - } - - gdk.ThreadsEnter() - for pos := range todelete { - if tile, ok := mw.maptiles[pos]; ok { - tile.Unref() - delete(mw.maptiles, pos) - } - - if tile, ok := mw.biotiles[pos]; ok { - tile.Unref() - delete(mw.biotiles, pos) - } - - if _, ok := mw.bioCache[pos]; ok { - delete(mw.bioCache, pos) - } - } - - if mw.region != nil { - for z := startZ; z < endZ; z++ { - scanX: - for x := startX; x < endX; x++ { - pos := XZPos{x, z} - - if _, ok := mw.biotiles[pos]; ok { - continue scanX - } - - chunk, err := mw.region.Chunk(x, z) - switch err { - case nil: - case mcmap.NotAvailable: - continue scanX - default: - mw.reportFail(fmt.Sprintf("Could not get chunk %d, %d: %s", x, z, err)) - return - } - - mw.maptiles[pos], mw.biotiles[pos], mw.bioCache[pos] = renderTile(chunk) - chunk.MarkUnused() - - gdk.ThreadsLeave() - mw.redraw <- true - gdk.ThreadsEnter() - } - } - } - - gdk.ThreadsLeave() - } - } -} - -func (mw *MapWidget) configure() { - if mw.pixmap != nil { - mw.pixmap.Unref() - } - - alloc := mw.dArea.GetAllocation() - mw.w = alloc.Width - mw.h = alloc.Height - - if !mw.isInit { - mw.offX = -(mw.w / 2) - mw.offZ = -(mw.h / 2) - mw.isInit = true - } - - mw.pixmap = gdk.NewPixmap(mw.dArea.GetWindow().GetDrawable(), mw.w, mw.h, 24) - mw.pixmapGC = gdk.NewGC(mw.pixmap.GetDrawable()) - - mw.drawBg() - gdk.ThreadsLeave() +func (mw *MapWidget) SetShowBiomes(b bool) { + mw.showBiomes = b mw.redraw <- true - gdk.ThreadsEnter() } -func (mw *MapWidget) drawBg() { - if mw.bg != nil { - mw.bg.Unref() - } +func (mw *MapWidget) SetFixSnowIce(b bool) { mw.regWrap.SetFixSnowIce(b) } +func (mw *MapWidget) SetBiome(bio mcmap.Biome) { mw.regWrap.SetBiome(bio) } +func (mw *MapWidget) SetRegion(region *mcmap.Region) { mw.regWrap.SetRegion(region) } +func (mw *MapWidget) SetTool(t Tool) { mw.regWrap.SetTool(t) } - mw.bg = emptyPixmap(mw.w, mw.h, 24) - drawable := mw.bg.GetDrawable() - gc := gdk.NewGC(drawable) +func (mw *MapWidget) Save() { mw.regWrap.Save() } - w := int(math.Ceil(float64(mw.w) / 32)) - h := int(math.Ceil(float64(mw.h) / 32)) - - for y := 0; y < h; y++ { - for x := 0; x < w; x++ { - if (x % 2) == (y % 2) { - gc.SetRgbFgColor(checker1) - } else { - gc.SetRgbFgColor(checker2) - } - drawable.DrawRectangle(gc, true, x*32, y*32, 32, 32) - } - } -} - -func (mw *MapWidget) compose() { - drawable := mw.pixmap.GetDrawable() - gc := mw.pixmapGC - - drawable.DrawDrawable(gc, mw.bg.GetDrawable(), 0, 0, 0, 0, -1, -1) - - var tiles map[XZPos]*gdk.Pixmap - if mw.showBiomes { - tiles = mw.biotiles - } else { - tiles = mw.maptiles - } - - for pos, tile := range tiles { - x := (pos.X * tileSize) - mw.offX - y := (pos.Z * tileSize) - mw.offZ - - drawable.DrawDrawable(gc, tile.GetDrawable(), 0, 0, x, y, tileSize, tileSize) - } -} - -func (mw *MapWidget) useTool(x, z int) { - gdk.ThreadsLeave() - defer gdk.ThreadsEnter() - - if !mw.toolsEnabled { - return - } - - if mw.tool.IsSlow() { - mw.toolsEnabled = false - mw.setBusy(true) - - go func() { - mw.tool.Do(mw.bio, mw, x, z) - mw.setBusy(false) - mw.toolsEnabled = true - - mw.redraw <- true - }() - } else { - mw.tool.Do(mw.bio, mw, x, z) - } +func (mw *MapWidget) updateChunkBounds() { + startX := int(math.Floor(float64(mw.offX) / tileSize)) + startZ := int(math.Floor(float64(mw.offZ) / tileSize)) + endX := int(math.Ceil(float64(mw.offX+mw.w) / tileSize)) + endZ := int(math.Ceil(float64(mw.offZ+mw.h) / tileSize)) + mw.regWrap.SetChunkBounds(startX, startZ, endX, endZ) } func (mw *MapWidget) movement(ctx *glib.CallbackContext) { @@ -331,12 +86,12 @@ func (mw *MapWidget) movement(ctx *glib.CallbackContext) { x := (mw.offX + mw.mx2) / zoom z := (mw.offZ + mw.my2) / zoom - cx, cz, cbx, cbz := mcmap.BlockToChunk(x, z) + bio := mcmap.Biome(mcmap.BioUncalculated) - if bc, ok := mw.bioCache[XZPos{cx, cz}]; ok { - bio = bc[cbz*mcmap.ChunkSizeXZ+cbx] + if _bio, ok := mw.regWrap.GetBiomeAt(x, z); ok { + bio = _bio } - mw.updateInfo(x, z, bio) + mw.guicbs.updateInfo(x, z, bio) if mw.panning { if (mw.mx1 != -1) && (mw.my1 != -1) { @@ -350,7 +105,7 @@ func (mw *MapWidget) movement(ctx *glib.CallbackContext) { } if mw.continueTool { - mw.useTool(x, z) + mw.regWrap.UseTool(x, z) gdk.ThreadsLeave() mw.redraw <- true @@ -360,10 +115,6 @@ func (mw *MapWidget) movement(ctx *glib.CallbackContext) { mw.mx1, mw.my1 = mw.mx2, mw.my2 } -func (mw *MapWidget) Save() { - mw.tileCmds <- cmdSave -} - func (mw *MapWidget) buttonChanged(ctx *glib.CallbackContext) { arg := ctx.Args(0) bev := *(**gdk.EventButton)(unsafe.Pointer(&arg)) @@ -373,8 +124,10 @@ func (mw *MapWidget) buttonChanged(ctx *glib.CallbackContext) { if mw.panning { mw.panning = false + mw.updateChunkBounds() + gdk.ThreadsLeave() - mw.tileCmds <- cmdUpdateTiles + mw.regWrap.UpdateTiles() gdk.ThreadsEnter() } @@ -382,18 +135,18 @@ func (mw *MapWidget) buttonChanged(ctx *glib.CallbackContext) { case gdk.BUTTON_PRESS: switch bev.Button { case 1: - if mw.region == nil { + if !mw.regWrap.RegionLoaded() { return } x := (mw.offX + int(bev.X)) / zoom z := (mw.offZ + int(bev.Y)) / zoom - mw.useTool(x, z) + mw.regWrap.UseTool(x, z) gdk.ThreadsLeave() mw.redraw <- true gdk.ThreadsEnter() - if !mw.tool.SingleClick() { + if !mw.regWrap.ToolSingleClick() { mw.continueTool = true } case 2: @@ -402,182 +155,116 @@ func (mw *MapWidget) buttonChanged(ctx *glib.CallbackContext) { } } -func (mw *MapWidget) expose() { - mw.dArea.GetWindow().GetDrawable().DrawDrawable(mw.pixmapGC, mw.pixmap.GetDrawable(), 0, 0, 0, 0, -1, -1) -} +var ( + checker1 = gdk.NewColor("#222222") + checker2 = gdk.NewColor("#444444") +) -func (mw *MapWidget) guiUpdater() { - for _ = range mw.redraw { - gdk.ThreadsEnter() - mw.compose() - mw.expose() - mw.dArea.GetWindow().Invalidate(nil, false) - gdk.ThreadsLeave() +func (mw *MapWidget) drawBg() { + if mw.bg != nil { + mw.bg.Unref() } -} - -func (mw *MapWidget) init() { - mw.redraw = make(chan bool) - mw.tileCmds = make(chan tileCmd) - - mw.maptiles = make(map[XZPos]*gdk.Pixmap) - mw.biotiles = make(map[XZPos]*gdk.Pixmap) - mw.bioCache = make(map[XZPos][]mcmap.Biome) - - mw.showBiomes = true - - mw.mx1, mw.my1 = -1, -1 - - go mw.doTileCmds() - go mw.guiUpdater() - - mw.dArea = gtk.NewDrawingArea() - mw.dArea.Connect("configure-event", mw.configure) - mw.dArea.Connect("expose-event", mw.expose) - mw.dArea.Connect("motion-notify-event", mw.movement) - mw.dArea.Connect("button-press-event", mw.buttonChanged) - mw.dArea.Connect("button-release-event", mw.buttonChanged) - - mw.dArea.SetEvents(int(gdk.POINTER_MOTION_MASK | gdk.POINTER_MOTION_HINT_MASK | gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK)) -} - -func (mw *MapWidget) setRegion(region *mcmap.Region) { - mw.tileCmds <- cmdFlushTiles - mw.region = NewCachedRegion(region, cacheSize) - mw.tileCmds <- cmdUpdateTiles -} -func (mw *MapWidget) GetBiomeAt(x, z int) (mcmap.Biome, bool) { - cx, cz, bx, bz := mcmap.BlockToChunk(x, z) - pos := XZPos{cx, cz} - - if bc, ok := mw.bioCache[pos]; ok { - return bc[bz*mcmap.ChunkSizeXZ+bx], true - } + mw.bg = emptyPixmap(mw.w, mw.h, 24) + drawable := mw.bg.GetDrawable() + gc := gdk.NewGC(drawable) - chunk, err := mw.region.Chunk(cx, cz) - switch err { - case nil: - case mcmap.NotAvailable: - return mcmap.BioUncalculated, false - default: - mw.reportFail(fmt.Sprintf("Error while getting chunk %d, %d: %s", cx, cz, err)) - return mcmap.BioUncalculated, false - } + w := int(math.Ceil(float64(mw.w) / 32)) + h := int(math.Ceil(float64(mw.h) / 32)) - bc := make([]mcmap.Biome, mcmap.ChunkRectXZ) - i := 0 - for z := 0; z < mcmap.ChunkSizeXZ; z++ { - for x := 0; x < mcmap.ChunkSizeXZ; x++ { - bc[i] = chunk.Biome(x, z) - i++ + for y := 0; y < h; y++ { + for x := 0; x < w; x++ { + if (x % 2) == (y % 2) { + gc.SetRgbFgColor(checker1) + } else { + gc.SetRgbFgColor(checker2) + } + drawable.DrawRectangle(gc, true, x*32, y*32, 32, 32) } } - mw.bioCache[pos] = bc - - return chunk.Biome(bx, bz), true } -func fixFreeze(bx, bz int, chunk *mcmap.Chunk) (newcol *gdk.Color) { - for y := chunk.Height(bx, bz); y >= 0; y-- { - if blk := chunk.Block(bx, y, bz); blk.ID != mcmap.BlkAir { - if (blk.ID == mcmap.BlkStationaryWater) || (blk.ID == mcmap.BlkWater) { - blk.ID = mcmap.BlkIce - newcol = blockColors[mcmap.BlkIce] - } else if blockCanSnowIn[blk.ID] { - if yFix := y + 1; yFix < mcmap.ChunkSizeY { - blkFix := chunk.Block(bx, yFix, bz) - blkFix.ID = mcmap.BlkSnow - blkFix.Data = 0x0 - newcol = blockColors[mcmap.BlkSnow] - } - } - - break - } +func (mw *MapWidget) configure() { + if mw.pixmap != nil { + mw.pixmap.Unref() } - return -} - -func fixMelt(bx, bz int, chunk *mcmap.Chunk) (newcol *gdk.Color) { - for y := chunk.Height(bx, bz); y >= 0; y-- { - if blk := chunk.Block(bx, y, bz); blk.ID != mcmap.BlkAir { - if blk.ID == mcmap.BlkIce { - blk.ID = mcmap.BlkStationaryWater - blk.Data = 0x0 - newcol = blockColors[mcmap.BlkStationaryWater] - } else if blk.ID == mcmap.BlkSnow { - blk.ID = mcmap.BlkAir - for y2 := y - 1; y2 >= 0; y2-- { - if col, ok := blockColors[chunk.Block(bx, y2, bz).ID]; ok { - newcol = col - break - } - } - } + alloc := mw.dArea.GetAllocation() + mw.w = alloc.Width + mw.h = alloc.Height - break - } + if !mw.isInit { + mw.offX = -(mw.w / 2) + mw.offZ = -(mw.h / 2) + mw.updateChunkBounds() + mw.isInit = true } - return + mw.pixmap = gdk.NewPixmap(mw.dArea.GetWindow().GetDrawable(), mw.w, mw.h, 24) + mw.pixmapGC = gdk.NewGC(mw.pixmap.GetDrawable()) + + mw.drawBg() + gdk.ThreadsLeave() + mw.redraw <- true + gdk.ThreadsEnter() } -func (mw *MapWidget) SetBiomeAt(x, z int, bio mcmap.Biome) { - cx, cz, bx, bz := mcmap.BlockToChunk(x, z) - pos := XZPos{cx, cz} - - chunk, err := mw.region.Chunk(cx, cz) - switch err { - case nil: - case mcmap.NotAvailable: - return - default: - mw.reportFail(fmt.Sprintf("Error while getting chunk %d, %d: %s", cx, cz, err)) - return - } +func (mw *MapWidget) compose() { + drawable := mw.pixmap.GetDrawable() + gc := mw.pixmapGC - chunk.SetBiome(bx, bz, bio) + drawable.DrawDrawable(gc, mw.bg.GetDrawable(), 0, 0, 0, 0, -1, -1) - var newcol *gdk.Color - if mw.fixSnowIce { - if coldBiome[bio] { - newcol = fixFreeze(bx, bz, chunk) - } else { - newcol = fixMelt(bx, bz, chunk) - } + var tiles map[XZPos]*gdk.Pixmap + if mw.showBiomes { + tiles = mw.regWrap.Biotiles + } else { + tiles = mw.regWrap.Maptiles } - chunk.MarkModified() + for pos, tile := range tiles { + x := (pos.X * tileSize) - mw.offX + y := (pos.Z * tileSize) - mw.offZ - // Update cache - if bc, ok := mw.bioCache[pos]; ok { - bc[bz*mcmap.ChunkSizeXZ+bx] = bio + drawable.DrawDrawable(gc, tile.GetDrawable(), 0, 0, x, y, tileSize, tileSize) } +} - // Update tile - if biotile, ok := mw.biotiles[pos]; ok { - gdk.ThreadsEnter() - - drawable := biotile.GetDrawable() - gc := gdk.NewGC(drawable) - gc.SetRgbFgColor(bioColors[bio]) - drawable.DrawRectangle(gc, true, bx*zoom, bz*zoom, zoom, zoom) - - if newcol != nil { - drawable = mw.maptiles[pos].GetDrawable() - gc = gdk.NewGC(drawable) - gc.SetRgbFgColor(newcol) - drawable.DrawRectangle(gc, true, bx*zoom, bz*zoom, zoom, zoom) - } +func (mw *MapWidget) expose() { + mw.dArea.GetWindow().GetDrawable().DrawDrawable(mw.pixmapGC, mw.pixmap.GetDrawable(), 0, 0, 0, 0, -1, -1) +} +func (mw *MapWidget) guiUpdater() { + for _ = range mw.redraw { + gdk.ThreadsEnter() + mw.compose() + mw.expose() + mw.dArea.GetWindow().Invalidate(nil, false) gdk.ThreadsLeave() } } -func NewMapWidget(reportFail func(msg string), updateInfo func(x, z int, bio mcmap.Biome), setBusy func(bool)) *MapWidget { - mw := &MapWidget{reportFail: reportFail, updateInfo: updateInfo, setBusy: setBusy, toolsEnabled: true} - mw.init() +func NewMapWidget(guicbs GUICallbacks) *MapWidget { + dArea := gtk.NewDrawingArea() + redraw := make(chan bool) + + mw := &MapWidget{ + dArea: dArea, + guicbs: guicbs, + redraw: redraw, + showBiomes: true, + regWrap: NewRegionWrapper(redraw, guicbs), + mx1: -1, + my1: -1, + } + go mw.guiUpdater() + + dArea.Connect("configure-event", mw.configure) + dArea.Connect("expose-event", mw.expose) + dArea.Connect("motion-notify-event", mw.movement) + dArea.Connect("button-press-event", mw.buttonChanged) + dArea.Connect("button-release-event", mw.buttonChanged) + dArea.SetEvents(int(gdk.POINTER_MOTION_MASK | gdk.POINTER_MOTION_HINT_MASK | gdk.BUTTON_PRESS_MASK | gdk.BUTTON_RELEASE_MASK)) + return mw } diff --git a/region_wrapper.go b/region_wrapper.go new file mode 100644 index 0000000..1139de5 --- /dev/null +++ b/region_wrapper.go @@ -0,0 +1,363 @@ +package main + +import ( + "fmt" + "github.com/kch42/gomcmap/mcmap" + "github.com/mattn/go-gtk/gdk" +) + +const cacheSize = 4 + +type RegionWrapper struct { + region *CachedRegion + + tileUpdates chan bool + + Maptiles map[XZPos]*gdk.Pixmap + Biotiles map[XZPos]*gdk.Pixmap + bioCache map[XZPos][]mcmap.Biome + + redraw chan<- bool + guicbs GUICallbacks + + toolsEnabled bool + tool Tool + + fixSnowIce bool + + bio mcmap.Biome + + startX, startZ, endX, endZ int +} + +func renderTile(chunk *mcmap.Chunk) (maptile, biotile *gdk.Pixmap, biocache []mcmap.Biome) { + maptile = emptyPixmap(tileSize, tileSize, 24) + mtDrawable := maptile.GetDrawable() + mtGC := gdk.NewGC(mtDrawable) + + biotile = emptyPixmap(tileSize, tileSize, 24) + btDrawable := biotile.GetDrawable() + btGC := gdk.NewGC(btDrawable) + + biocache = make([]mcmap.Biome, mcmap.ChunkRectXZ) + + i := 0 + for z := 0; z < mcmap.ChunkSizeXZ; z++ { + scanX: + for x := 0; x < mcmap.ChunkSizeXZ; x++ { + bio := chunk.Biome(x, z) + btGC.SetRgbFgColor(bioColors[bio]) + btDrawable.DrawRectangle(btGC, true, x*zoom, z*zoom, zoom, zoom) + + biocache[i] = bio + i++ + + for y := chunk.Height(x, z); y >= 0; y-- { + if col, ok := blockColors[chunk.Block(x, y, z).ID]; ok { + mtGC.SetRgbFgColor(col) + mtDrawable.DrawRectangle(mtGC, true, x*zoom, z*zoom, zoom, zoom) + continue scanX + } + } + + mtGC.SetRgbFgColor(gdk.NewColor("#ffffff")) + mtDrawable.DrawRectangle(mtGC, true, x*zoom, z*zoom, zoom, zoom) + } + } + + return +} + +func (rw *RegionWrapper) tileUpdater() { + for _ = range rw.tileUpdates { + todelete := make(map[XZPos]bool) + + for pos := range rw.Maptiles { + if (pos.X < rw.startX) || (pos.Z < rw.startZ) || (pos.X >= rw.endX) || (pos.Z >= rw.endZ) { + todelete[pos] = true + } + } + + gdk.ThreadsEnter() + for pos := range todelete { + if tile, ok := rw.Maptiles[pos]; ok { + tile.Unref() + delete(rw.Maptiles, pos) + } + + if tile, ok := rw.Biotiles[pos]; ok { + tile.Unref() + delete(rw.Biotiles, pos) + } + + if _, ok := rw.bioCache[pos]; ok { + delete(rw.bioCache, pos) + } + } + + if rw.region != nil { + for z := rw.startZ; z < rw.endZ; z++ { + scanX: + for x := rw.startX; x < rw.endX; x++ { + pos := XZPos{x, z} + + if _, ok := rw.Biotiles[pos]; ok { + continue scanX + } + + chunk, err := rw.region.Chunk(x, z) + switch err { + case nil: + case mcmap.NotAvailable: + continue scanX + default: + rw.guicbs.reportFail(fmt.Sprintf("Could not get chunk %d, %d: %s", x, z, err)) + return + } + + rw.Maptiles[pos], rw.Biotiles[pos], rw.bioCache[pos] = renderTile(chunk) + chunk.MarkUnused() + + gdk.ThreadsLeave() + rw.redraw <- true + gdk.ThreadsEnter() + } + } + } + + gdk.ThreadsLeave() + } +} + +func (rw *RegionWrapper) SetRegion(region *mcmap.Region) { + if rw.RegionLoaded() { + rw.flushTiles() + } + rw.region = NewCachedRegion(region, cacheSize) + + rw.tileUpdates <- true +} + +func (rw *RegionWrapper) SetChunkBounds(startX, startZ, endX, endZ int) { + rw.startX = startX + rw.startZ = startZ + rw.endX = endX + rw.endZ = endZ +} + +func (rw *RegionWrapper) SetTool(t Tool) { rw.tool = t } +func (rw *RegionWrapper) SetBiome(bio mcmap.Biome) { rw.bio = bio } +func (rw *RegionWrapper) SetFixSnowIce(b bool) { rw.fixSnowIce = b } + +func (rw *RegionWrapper) RegionLoaded() bool { return rw.region != nil } +func (rw *RegionWrapper) ToolSingleClick() bool { return rw.tool.SingleClick() } + +func (rw *RegionWrapper) flushTiles() { + if err := rw.region.Flush(); err != nil { + rw.guicbs.reportFail(fmt.Sprintf("Error while flushing cache: %s", err)) + return + } + + gdk.ThreadsEnter() + for _, mt := range rw.Maptiles { + mt.Unref() + } + for _, bt := range rw.Biotiles { + bt.Unref() + } + gdk.ThreadsLeave() + + rw.Maptiles = make(map[XZPos]*gdk.Pixmap) + rw.Biotiles = make(map[XZPos]*gdk.Pixmap) + rw.bioCache = make(map[XZPos][]mcmap.Biome) +} + +func (rw *RegionWrapper) Save() { + gdk.ThreadsLeave() + rw.flushTiles() + gdk.ThreadsEnter() + + if err := rw.region.Flush(); err != nil { + rw.guicbs.reportFail(fmt.Sprintf("Error while flushing cache: %s", err)) + return + } + if err := rw.region.Region.Save(); err != nil { + rw.guicbs.reportFail(fmt.Sprintf("Error while saving: %s", err)) + return + } +} + +func (rw *RegionWrapper) UseTool(x, z int) { + gdk.ThreadsLeave() + defer gdk.ThreadsEnter() + + if !rw.toolsEnabled { + return + } + + if rw.tool.IsSlow() { + rw.toolsEnabled = false + rw.guicbs.setBusy(true) + + go func() { + rw.tool.Do(rw.bio, rw, x, z) + rw.guicbs.setBusy(false) + rw.toolsEnabled = true + + rw.redraw <- true + }() + } else { + rw.tool.Do(rw.bio, rw, x, z) + } +} + +func (rw *RegionWrapper) GetBiomeAt(x, z int) (mcmap.Biome, bool) { + cx, cz, bx, bz := mcmap.BlockToChunk(x, z) + pos := XZPos{cx, cz} + + if bc, ok := rw.bioCache[pos]; ok { + return bc[bz*mcmap.ChunkSizeXZ+bx], true + } + + if !rw.RegionLoaded() { + return mcmap.BioUncalculated, false + } + + chunk, err := rw.region.Chunk(cx, cz) + switch err { + case nil: + case mcmap.NotAvailable: + return mcmap.BioUncalculated, false + default: + rw.guicbs.reportFail(fmt.Sprintf("Error while getting chunk %d, %d: %s", cx, cz, err)) + return mcmap.BioUncalculated, false + } + + bc := make([]mcmap.Biome, mcmap.ChunkRectXZ) + i := 0 + for z := 0; z < mcmap.ChunkSizeXZ; z++ { + for x := 0; x < mcmap.ChunkSizeXZ; x++ { + bc[i] = chunk.Biome(x, z) + i++ + } + } + rw.bioCache[pos] = bc + + return chunk.Biome(bx, bz), true +} + +func fixFreeze(bx, bz int, chunk *mcmap.Chunk) (newcol *gdk.Color) { + for y := chunk.Height(bx, bz); y >= 0; y-- { + if blk := chunk.Block(bx, y, bz); blk.ID != mcmap.BlkAir { + if (blk.ID == mcmap.BlkStationaryWater) || (blk.ID == mcmap.BlkWater) { + blk.ID = mcmap.BlkIce + newcol = blockColors[mcmap.BlkIce] + } else if blockCanSnowIn[blk.ID] { + if yFix := y + 1; yFix < mcmap.ChunkSizeY { + blkFix := chunk.Block(bx, yFix, bz) + blkFix.ID = mcmap.BlkSnow + blkFix.Data = 0x0 + newcol = blockColors[mcmap.BlkSnow] + } + } + + break + } + } + + return +} + +func fixMelt(bx, bz int, chunk *mcmap.Chunk) (newcol *gdk.Color) { + for y := chunk.Height(bx, bz); y >= 0; y-- { + if blk := chunk.Block(bx, y, bz); blk.ID != mcmap.BlkAir { + if blk.ID == mcmap.BlkIce { + blk.ID = mcmap.BlkStationaryWater + blk.Data = 0x0 + newcol = blockColors[mcmap.BlkStationaryWater] + } else if blk.ID == mcmap.BlkSnow { + blk.ID = mcmap.BlkAir + for y2 := y - 1; y2 >= 0; y2-- { + if col, ok := blockColors[chunk.Block(bx, y2, bz).ID]; ok { + newcol = col + break + } + } + } + + break + } + } + + return +} + +func (rw *RegionWrapper) SetBiomeAt(x, z int, bio mcmap.Biome) { + cx, cz, bx, bz := mcmap.BlockToChunk(x, z) + pos := XZPos{cx, cz} + + chunk, err := rw.region.Chunk(cx, cz) + switch err { + case nil: + case mcmap.NotAvailable: + return + default: + rw.guicbs.reportFail(fmt.Sprintf("Error while getting chunk %d, %d: %s", cx, cz, err)) + return + } + + chunk.SetBiome(bx, bz, bio) + + var newcol *gdk.Color + if rw.fixSnowIce { + if coldBiome[bio] { + newcol = fixFreeze(bx, bz, chunk) + } else { + newcol = fixMelt(bx, bz, chunk) + } + } + + chunk.MarkModified() + + // Update cache + if bc, ok := rw.bioCache[pos]; ok { + bc[bz*mcmap.ChunkSizeXZ+bx] = bio + } + + // Update tile + if biotile, ok := rw.Biotiles[pos]; ok { + gdk.ThreadsEnter() + + drawable := biotile.GetDrawable() + gc := gdk.NewGC(drawable) + gc.SetRgbFgColor(bioColors[bio]) + drawable.DrawRectangle(gc, true, bx*zoom, bz*zoom, zoom, zoom) + + if newcol != nil { + drawable = rw.Maptiles[pos].GetDrawable() + gc = gdk.NewGC(drawable) + gc.SetRgbFgColor(newcol) + drawable.DrawRectangle(gc, true, bx*zoom, bz*zoom, zoom, zoom) + } + + gdk.ThreadsLeave() + } +} + +func (rw *RegionWrapper) UpdateTiles() { + rw.tileUpdates <- true +} + +func NewRegionWrapper(redraw chan<- bool, guicbs GUICallbacks) *RegionWrapper { + rw := &RegionWrapper{ + redraw: redraw, + tileUpdates: make(chan bool), + toolsEnabled: true, + guicbs: guicbs, + Maptiles: make(map[XZPos]*gdk.Pixmap), + Biotiles: make(map[XZPos]*gdk.Pixmap), + bioCache: make(map[XZPos][]mcmap.Biome), + } + go rw.tileUpdater() + return rw +} -- cgit v1.2.3-54-g00ecf