美文网首页
Go项目的系统初始化设计-游戏版

Go项目的系统初始化设计-游戏版

作者: 筑梦之队 | 来源:发表于2020-09-07 10:04 被阅读0次

Go中提供了一种初始化数据和逻辑的方式:func init() {}。当我们要初始化配置文件,或者是要启动一个goroutine,都可以在对应模块的init方法中进行。
而init方法执行的时机在于该package被第一次访问时。在程序中,我们有两种方式来触发指定package内的init方法的执行:

  1. 显示地import package来,如:import github.com/Jordanzuo/goutil/baseUtil
  2. 使用_的方式来import package,如:import _github.com/Jordanzuo/goutil/baseUtil

在第一种方式中,需要在import package的文件中显示地调用baseUtil的方法,否则会编译失败;而在第二种方式中,则没有这个限制。因为import _ package的设计目的就是为了方便在不使用package内容的情况下,让其内部的init方法得到执行。

看起来很美好。只要各个package之间互相之间没有依赖关系,则各个模块可以独自地进行数据和方法和初始化。但是在一个规模稍微大一些的系统中,模块和模块之间或多或少地存在着一些依赖关系,比如模块A依赖于系统配置模块。那么在系统配置模块初始化完成前进行模块A的初始化,则会出现空引用的异常。

这个问题如何解决呢?我们以游戏为例来说明此问题的解决方案。
在游戏中,需要进行初始化的内容包括两大类:数据和goroutine。其中:

  • 数据:游戏中的各类数据;数据内部有依赖关系。
  • goroutine:用于控制系统流程的一些独立goroutine;大多数时间都依赖于数据的初始化完成。

而数据又可以分为4类:

  1. 静态的配置数据:在整个程序的生命周期内保持不变的配置数据。
  2. 动态的配置数据:在整个程序的生命周期内会改变的配置数据。
  3. 玩家相关数据:只由玩家自己改变的数据。如玩家的装备、随从等数据。
  4. 全局数据:任何玩家都可以改变的数据。如boss、竞技场等数据。

为了保证数据的一致性和程序运行的正确性,我们对于以上的4类数据施加了一些限制:

  1. 对于静态的配置数据,我们必须在系统启动阶段,验证数据的准确性;如果发现有错误数据,则不允许程序启动。而数据准确性的保证,则依赖于模块之间数据的彼此验证。比如:装备升级表中包含了装备的模型ID,那么我们就需要保证该模型ID一定存在于装备的模型表中。
  2. 对于动态的配置数据,除了在系统启动阶段,验证的数据准确性之外;在系统运行期间,每当数据发生变化,我们在将数据应用到系统前,都需要验证数据的准确性。
  3. 对于玩家相关数据,我们一般只会将最核心的玩家数据加载到内存中,而剩余的数据则进行延迟加载。
  4. 对于全局数据,需要将全部数据都加载到内存中,并进行适当的验证。

针对以上的需求,我设计了以下的初始化管理系统。

  • 将系统的初始化分解成8个步骤:
  1. Load: 从数据库中加载数据到内存中的临时变量中。
  2. Verify: 验证临时变量中的数据的正确性;比如装备等级表中的模型ID必须存在于装备模型配置表中。
  3. Convert: 对临时变量中的数据进行必要的转换;比如将,分隔的字符串转换为[]int。
  4. Apply: 将验证和转换后的临时数据赋值给正式数据变量。
  5. Print: 打印数据。
  6. Clean: 清理临时数据。到此数据加载完成。
  7. Setup: 设置系统运行环境。例如:启动一个持续运行的goroutine。
  8. Complete: 系统初始化完成后执行的一些操作。例如:注册小红点处理逻辑。
  • 在系统中提供LoadMgr, VerifyMgr, ConvertMgr, ApplyMgr, PrintMgr, CleanMgr, SetupMgr, CompleteMgr等8个对象。
  • 在各个package的init方法中,根据实际需要,将自己内部的方法分别注册到初始化管理系统对应的对象中。如:
    bll.go
func init() {
    systemInitMgr.LoadMgr.RegisterFunc("load", load)
    systemInitMgr.VerifyMgr.RegisterFunc("verify", verify)
    systemInitMgr.ConvertMgr.RegisterFunc("convert", convert)
    systemInitMgr.ApplyMgr.RegisterFunc("apply", apply)
    systemInitMgr.PrintMgr.RegisterFunc("print", print)
    systemInitMgr.CleanMgr.RegisterFunc("clean", clean)
    systemInitMgr.SetupMgr.RegisterFunc("setup", setup)
    systemInitMgr.CompleteMgr.RegisterFunc("complete", complete)
}

func load() []error {}
func verify() []error {}
func convert() []error {}
func apply() []error {}
func print() []error {}
func clean() []error {}
func setup() []error {}
func complete() []error {}

核心代码如下:
model.go

package systemInitMgr

// 方法对象
type FuncItem struct {
    // 模块名称
    moduleName string

    // 方法定义
    funcDefinition func() []error
}

func newFuncItem(moduleName string, funcDefinition func() []error) *FuncItem {
    return &FuncItem{
        moduleName:     moduleName,
        funcDefinition: funcDefinition,
    }
}

load.go

package systemInitMgr

import (
    "strings"
    "sync"
)

var (
    LoadMgr = newLoadMgr()
)

type loadMgr struct {
    funcList []*FuncItem
    mutex    sync.Mutex
}

func newLoadMgr() *loadMgr {
    return &loadMgr{
        funcList: make([]*FuncItem, 0, 64),
    }
}

func (this *loadMgr) RegisterFunc(moduleName string, funcDefinition func() []error) {
    this.mutex.Lock()
    defer this.mutex.Unlock()

    funcItemObj := newFuncItem(moduleName, funcDefinition)
    this.funcList = append(this.funcList, funcItemObj)
}

func (this *loadMgr) CallByModuleName(moduleName string) (errList []error) {
    this.mutex.Lock()
    defer this.mutex.Unlock()

    for _, item := range this.funcList {
        if item.moduleName != moduleName {
            continue
        }

        errorList := item.funcDefinition()
        if errorList != nil && len(errorList) > 0 {
            errList = append(errList, errorList...)
        }
    }

    return
}

func (this *loadMgr) CallByModuleNamePrefix(moduleNamePrefix string) (errList []error) {
    this.mutex.Lock()
    defer this.mutex.Unlock()

    for _, item := range this.funcList {
        if strings.HasPrefix(item.moduleName, moduleNamePrefix) == false {
            continue
        }

        errorList := item.funcDefinition()
        if errorList != nil && len(errorList) > 0 {
            errList = append(errList, errorList...)
        }
    }

    return
}

func (this *loadMgr) CallAll() (errList []error) {
    this.mutex.Lock()
    defer this.mutex.Unlock()

    for _, item := range this.funcList {
        errorList := item.funcDefinition()
        if errorList != nil && len(errorList) > 0 {
            errList = append(errList, errorList...)
        }
    }

    return
}

systemInitMgr.go

package systemInitMgr

import (
    "fmt"
)

var (
    serverModuleName = "systemInitMgr"
)

func StartProcess() {
    totalErrList := make([]error, 0, 16)

    // 从数据库中加载所有的配置数据
    loadErrList := LoadMgr.CallAll()
    if len(loadErrList) > 0 {
        totalErrList = append(totalErrList, loadErrList...)
    }

    // 验证所有的数据是否合法
    verifyErrList := VerifyMgr.CallAll()
    if len(verifyErrList) > 0 {
        totalErrList = append(totalErrList, verifyErrList...)
    }

    // 将所有的配置数据进行转换(如果需要)
    convertErrList := ConvertMgr.CallAll()
    if len(convertErrList) > 0 {
        totalErrList = append(totalErrList, convertErrList...)
    }

    // 将验证后的数据应用到正式数据中
    applyErrList := ApplyMgr.CallAll()
    if len(applyErrList) > 0 {
        totalErrList = append(totalErrList, applyErrList...)
    }

    // 打印数据
    printErrList := PrintMgr.CallAll()
    if len(printErrList) > 0 {
        totalErrList = append(totalErrList, printErrList...)
    }

    // 清理数据(临时变量)
    cleanErrList := CleanMgr.CallAll()
    if len(cleanErrList) > 0 {
        totalErrList = append(totalErrList, cleanErrList...)
    }

    // 设置环境
    setupErrList := SetupMgr.CallAll()
    if len(setupErrList) > 0 {
        totalErrList = append(totalErrList, setupErrList...)
    }

    // 初始化完成
    completeErrList := CompleteMgr.CallAll()
    if len(completeErrList) > 0 {
        totalErrList = append(totalErrList, completeErrList...)
    }

    // 检查全部的错误信息
    if len(totalErrList) > 0 {
        fmt.Println("-------------------------------------------------------------------------------------")
        fmt.Printf("There are %d error(s):\n", len(totalErrList))
        for i, v := range totalErrList {
            fmt.Printf("No.%d: %v\n", i+1, v)
        }
        panic("Please fix them first.")
    }
}

相关文章

  • Go项目的系统初始化设计-游戏版

    Go中提供了一种初始化数据和逻辑的方式:func init() {}。当我们要初始化配置文件,或者是要启动一个go...

  • 游戏逻辑系统化

    游戏逻辑系统化 1.游戏逻辑的目的实现策划的设计文档 构建游戏系统 构建游戏玩法 2.什么是游戏逻辑游戏逻辑是作为...

  • Go语言基础练习四

    结构体版学生管理系统 student_magr.go main.go

  • go程序启动时执行安装install初始化数据

    go程序启动时执行安装install初始化数据 目的 程序启动执行install 安装初始化程序,如初始化mysq...

  • filecoin

    【go-filecoin】 编译安装 安装环境 系统:使用的是ubuntu 18.03.1 64位版本 go语言环...

  • 区块链 | geth版以太坊开发环境搭建

    目录 说明 Go语言版的geth Win10系统 Go 下载Go 安装 环境配置 提示:特别注意,我选择是默认安装...

  • Golang使用Glide进行包管理之命令

    安装 初始化 在go项目的根目录下直接运行: 添加依赖 glide get,类似于go get,不过不同的是,gl...

  • BOY在学习设计模式

    Bruce Ouyang 正在学习《设计模式Java版》 http://woquanke.com/books/go...

  • 说说游戏版号这些事

    游戏版号其实就是网络游戏想要上线运营所必备的两项游戏备案之一——游戏出版备案,游戏出版备案又称游戏版号申请,还有一...

  • 游戏版号的点点滴滴

    游戏版号其实就是网络游戏想要上线运营所必备的两项游戏备案之一——游戏出版备案,游戏出版备案又称游戏版号申请,还有一...

网友评论

      本文标题:Go项目的系统初始化设计-游戏版

      本文链接:https://www.haomeiwen.com/subject/msttektx.html