Rollback #1135
Replies: 6 comments 2 replies
|
Not like Im not interested. Completely the opposite. Rollback is great. Because this is something that the engine has to handle instead of the rollback code that is being added. |
|
Just a few updates on the rollback code itself: Lua State saving/loading was accomplished via Userdata. An interface was built for registering lua data to the engine. When data is changed in Lua, the engine must be notified by "::set" methods, and when data is used in Lua, the newest version of the data must be returned by the engine via "::get" methods. Code has not been uploaded to Github but it looks like this: // Recursive function to convert Lua table to Go slice or map
func luaToGo(lv lua.LValue) interface{} {
switch lTable := lv.(type) {
case *lua.LTable:
if lTable.MaxN() > 0 { // It's an array-like table
goSlice := make([]interface{}, lTable.MaxN())
lTable.ForEach(func(key, value lua.LValue) {
index := int(key.(lua.LNumber)) - 1
goSlice[index] = luaToGo(value) // Recursive call for nested tables
})
return goSlice
} else { // It's a dictionary-like table
goMap := make(map[string]interface{})
lTable.ForEach(func(key, value lua.LValue) {
goMap[key.String()] = luaToGo(value) // Recursive call for nested tables
})
return goMap
}
case lua.LString:
return string(lTable)
case lua.LNumber:
return float64(lTable)
case lua.LBool:
return bool(lTable)
case *lua.LFunction: // Handle Lua functions
return lTable
default:
return nil
}
}
// Recursive function to convert Go slice or map to Lua table
func goToLua(L *lua.LState, gv interface{}) lua.LValue {
switch goValue := gv.(type) {
case []interface{}: // It's a Go slice
luaTable := L.CreateTable(len(goValue), 0)
for i, v := range goValue {
luaTable.RawSetInt(i+1, goToLua(L, v)) // Recursive call for nested slices or maps
}
return luaTable
case map[string]interface{}: // It's a Go map
luaTable := L.CreateTable(0, len(goValue))
for k, v := range goValue {
luaTable.RawSetString(k, goToLua(L, v)) // Recursive call for nested slices or maps
}
return luaTable
case string:
return lua.LString(goValue)
case float64:
return lua.LNumber(goValue)
case bool:
return lua.LBool(goValue)
case *lua.LFunction: // Handle Lua functions
return goValue
default:
return lua.LNil
}
}
const luaGameVarMT = "lua_game_var_mt"
type LuaGameVar struct {
data interface{}
}
func luaGameVarNew(L *lua.LState) int {
name := strArg(L, 1)
var value interface{} = nil
if L.GetTop() == 2 {
value = luaToGo(L.Get(2))
}
userdata := newUserData(L, &LuaGameVar{data: value})
// Set the metatable for the userdata
L.SetMetatable(userdata, L.GetTypeMetatable(luaGameVarMT))
sys.luaGameStateTable[name] = userdata
// Push userdata onto the stack and return 1
L.Push(userdata)
return 1
}
func luaGameVarGet(L *lua.LState) int {
userdata := toUserData(L, 1)
if v, ok := userdata.(*LuaGameVar); ok {
switch data := v.data.(type) {
case lua.LValue:
L.Push(data)
case nil:
L.Push(lua.LNil)
default:
// Handle error, v.data is not a lua.LValue or nil
}
//L.Push(v.data.(lua.LValue)) //goToLua(L, v.data))
} else {
userDataError(L, 1, userdata)
}
return 1
}
func luaGameVarSet(L *lua.LState) int {
userdata := toUserData(L, 1)
value := L.Get(2)
if v, ok := userdata.(*LuaGameVar); ok {
if lv, ok := value.(lua.LValue); ok {
v.data = lv
// Update the game state table
for k, ud := range sys.luaGameStateTable {
if lud, ok := ud.(*lua.LUserData); ok && lud.Value == v {
sys.luaGameStateTable[k] = lud
break
}
}
} else {
// Handle error, value is not a lua.LValue
}
} else {
userDataError(L, 1, userdata)
}
return 0
}
// Methods for the game variables
var luaGameVarMethods = map[string]lua.LGFunction{
"get": luaGameVarGet,
"set": luaGameVarSet,
}
func cloneState() map[string]*LuaGameVar {
clone := make(map[string]*LuaGameVar)
for k, v := range sys.luaGameStateTable {
if lu, ok := v.(*lua.LUserData); ok {
if lv, ok := lu.Value.(*LuaGameVar); ok {
clone[k] = lv.Clone()
}
}
}
return clone
}
func restoreState(clonedState map[string]*LuaGameVar) {
for k, v := range clonedState {
//userdata := sys.luaLState.NewUserData()
//userdata.Value = v
//sys.luaLState.SetMetatable(userdata, sys.luaLState.GetTypeMetatable(luaGameVarMT))
state := sys.luaGameStateTable[k].(*lua.LUserData)
state.Value = v
sys.luaGameStateTable[k] = state
}
}
func (v *LuaGameVar) Clone() *LuaGameVar {
switch data := v.data.(type) {
case lua.LValue:
return &LuaGameVar{data: cloneLuaValue(data)}
default:
return &LuaGameVar{data: data}
}
}
func cloneLuaValue(lv lua.LValue) lua.LValue {
switch v := lv.(type) {
case *lua.LTable:
clone := &lua.LTable{}
v.ForEach(func(key, value lua.LValue) {
clone.RawSet(cloneLuaValue(key), cloneLuaValue(value))
})
return clone
default:
return v
}
}
...
luaRegister(l, "gameStateNew", luaGameVarNew)
luaRegister(l, "gameStateGet", luaGameVarGet)
luaRegister(l, "gameStateSet", luaGameVarSet)
...
// Define the metatable
mt := l.NewTypeMetatable(luaGameVarMT)
l.SetField(mt, "__index", l.SetFuncs(l.NewTable(), luaGameVarMethods))GPT was a huge help in the process of iterating through solutions and tailoring them to our needs. |
|
Things that have to be working properly in order to internalize rollback:
|
|
After dealing with life issues I'm back. Firstly, I feel doing this particular feature justice both now and in the future requires me mastering Git, so I'm taking some time to focus on that. After that point I will focus on dealing with my fork which has been terribly mismanaged thus far. |
|
Put together a quick release pulling in RollinJay's branch and the newest changes from master. Have some notes but really need to sleep now. Be back later. https://github.com/assemblaj/Ikemen-GO/releases/tag/0.2.8 Edit: Haven't received any feedback on the newest release yet, regardless. Learning Git is a game changer. Before, I truly resented keeping the rollback branch updated. Now, I can easily isolate what is and isn't being accounted for in the rollback save state and logic with git diff, grep, log etc and a little scripting and ingenuity. I'm interested in having a Also, poking around on Git, I noticed that updating RollinJay's branch and even internalizing their changes on my own fork were all fast-forward merges with no conflicts. It's made me realize that despite not quite understanding Git, my decision to keep the rollback complete separate from the regular game loop and to keep rollback files in separate files, means that the rollback has a limited impact on the codebase at large in terms of potential for conflicts. This means that I don't think it could really hurt anything if it were on |
|
Taking note of a few bugs reported by Orange Dolphin
|
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
For discussion of the implementation of the rollback and planning its integration into the main repository. Main reasoning is to have a record of discussions related to the rollback such that they're easily accessible in the future.
All reactions