feat(state): 迁移登录邀请切片 reducer(InviteState)
1) 动作在 Kotlin 侧不再要求公共基类:原包动作也没有基类,Store/Gateway 改用 Any,817 个生成类无需实现接口。 2) 新增 App/State/Reducers/InviteReducer.kt,逐条对照 module_10032:状态 aD=InviteState(isInviteNeed, invite, status, existsUid, inviteAccepted)、枚举 lD=InviteStatus(UNKNOWN/FREE/NOT_FOUND/ALREADY_ACTIVATED)、reducer pD.er5 的三条分支。默认值取自构造函数(isInviteNeed 默认 true、invite 默认空串、status 默认 UNKNOWN、inviteAccepted 默认 false)。 3) 自检 19 增到 26 项:默认值、三条动作分支(含 FREE 推导 inviteAccepted、ApplyInvite 重置 status/existsUid)、无关动作不动、接 Store 派发后归约。 结果:TANKIOFFLINE_STATE_TEST=1 全通过。
This commit is contained in:
1 parent
6a07f9a39f
commit
b4ecb80b23
3 files changed
+128
-18
No files matched your search
@@ -0,0 +1,67 @@
|
||||
// InviteReducer.kt —— 登录邀请切片(对应 module_10032 的 aD 状态 + pD.er5 reducer)
|
||||
//
|
||||
// 证据链:
|
||||
// 状态类 aD:`function aD(t, n, i, r, e) { t = t === cd || t; n = n === cd ? "" : n;
|
||||
// i = i === cd ? wD() : i; r = r === cd ? null : r; e = e !== cd && e; … }`
|
||||
// toString:`InviteState(isInviteNeed=…, invite=…, status=…, existsUid=…, inviteAccepted=…)`
|
||||
// 枚举 lD(cD()):UNKNOWN / FREE / NOT_FOUND / ALREADY_ACTIVATED
|
||||
// reducer pD.er5:
|
||||
// SetInviteNeed → copy(inviteNeed)
|
||||
// SetInviteStatus → copy(cd, cd, inviteStatus, existUid, inviteStatus.equals(FREE))
|
||||
// ApplyInvite → copy(cd, invite, UNKNOWN, null)
|
||||
// 其它 → 原状态
|
||||
package App.State.Reducers
|
||||
|
||||
import App.State.ApplyInvite
|
||||
import App.State.SetInviteNeed
|
||||
import App.State.SetInviteStatus
|
||||
|
||||
/** 原 `lD`(module_10032 的邀请状态枚举)。 */
|
||||
enum class InviteStatus {
|
||||
UNKNOWN,
|
||||
FREE,
|
||||
NOT_FOUND,
|
||||
ALREADY_ACTIVATED,
|
||||
;
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* 动作字段在运行期是 JS 枚举对象,toString 就是成员名(`SetInviteStatus(inviteStatus=UNKNOWN…)`)。
|
||||
* 认不出时按原逻辑落到默认值 UNKNOWN。
|
||||
*/
|
||||
fun FromText(Text: Any?): InviteStatus =
|
||||
entries.firstOrNull { it.name == Text?.toString() } ?: UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 原 `aD`:`InviteState(isInviteNeed, invite, status, existsUid, inviteAccepted)`。
|
||||
* 字段类型只在有证据处收紧:isInviteNeed / inviteAccepted 由 reducer 逻辑确定为 Boolean,
|
||||
* invite / existsUid 运行时还没采到,保持宽松。
|
||||
*/
|
||||
data class InviteStateSlice(
|
||||
val IsInviteNeed: Boolean = true,
|
||||
val Invite: Any? = "",
|
||||
val Status: InviteStatus = InviteStatus.UNKNOWN,
|
||||
val ExistsUid: Any? = null,
|
||||
val InviteAccepted: Boolean = false,
|
||||
)
|
||||
|
||||
/** 原 `pD.er5`。 */
|
||||
fun ReduceInvite(State0: InviteStateSlice, A: Any): InviteStateSlice = when (A) {
|
||||
is SetInviteNeed -> State0.copy(IsInviteNeed = A.inviteNeed)
|
||||
is SetInviteStatus -> {
|
||||
val Status = InviteStatus.FromText(A.inviteStatus)
|
||||
State0.copy(
|
||||
Status = Status,
|
||||
ExistsUid = A.existUid,
|
||||
InviteAccepted = Status == InviteStatus.FREE,
|
||||
)
|
||||
}
|
||||
is ApplyInvite -> State0.copy(
|
||||
Invite = A.invite,
|
||||
Status = InviteStatus.UNKNOWN,
|
||||
ExistsUid = null,
|
||||
)
|
||||
else -> State0
|
||||
}
|
||||
@@ -10,16 +10,21 @@
|
||||
// 用法:$env:TANKIOFFLINE_STATE_TEST='1'; node …(见 TankiOffline/tools/RunStateTest.ps1)
|
||||
package App
|
||||
|
||||
import State.Action
|
||||
import State.Middleware
|
||||
import State.ReduxGateway
|
||||
import State.ReduxStore
|
||||
import State.StoreListener
|
||||
import App.State.ActionCatalog
|
||||
import App.State.ActionSamples
|
||||
import App.State.ApplyInvite
|
||||
import App.State.Reducers.InviteStateSlice
|
||||
import App.State.Reducers.InviteStatus
|
||||
import App.State.Reducers.ReduceInvite
|
||||
import App.State.SetInviteNeed
|
||||
import App.State.SetInviteStatus
|
||||
|
||||
private data class AddFrags(val fragsCount: Int) : Action
|
||||
private data class SetScreen(val screen: String) : Action
|
||||
private data class AddFrags(val fragsCount: Int)
|
||||
private data class SetScreen(val screen: String)
|
||||
|
||||
private data class UiState(val Frags: Int = 0, val Screen: String = "Preloader")
|
||||
|
||||
@@ -213,6 +218,47 @@ object StateCheck {
|
||||
Check("网关:全目录 ${ActionCatalog.All.size} 个动作类可注册且只路由命中项", Hits == 1)
|
||||
}
|
||||
|
||||
// ⑨ 切片 reducer:InviteState(原 module_10032 的 aD / pD.er5)
|
||||
run {
|
||||
val S0 = InviteStateSlice()
|
||||
Check(
|
||||
"切片 Invite:默认值与原构造一致",
|
||||
S0.IsInviteNeed && S0.Invite == "" && S0.Status == InviteStatus.UNKNOWN &&
|
||||
S0.ExistsUid == null && !S0.InviteAccepted
|
||||
)
|
||||
|
||||
val S1 = ReduceInvite(S0, SetInviteNeed(false))
|
||||
Check("切片 Invite:SetInviteNeed 只改 isInviteNeed", !S1.IsInviteNeed && S1.Status == S0.Status)
|
||||
|
||||
val S2 = ReduceInvite(S1, SetInviteStatus("FREE", 12345L))
|
||||
Check(
|
||||
"切片 Invite:SetInviteStatus 更新 status/existsUid/inviteAccepted",
|
||||
S2.Status == InviteStatus.FREE && S2.ExistsUid == 12345L && S2.InviteAccepted
|
||||
)
|
||||
|
||||
val S3 = ReduceInvite(S2, SetInviteStatus("NOT_FOUND", null))
|
||||
Check("切片 Invite:非 FREE 状态 inviteAccepted 归 false", !S3.InviteAccepted && S3.Status == InviteStatus.NOT_FOUND)
|
||||
|
||||
val S4 = ReduceInvite(S3, ApplyInvite("INVITE-CODE"))
|
||||
Check(
|
||||
"切片 Invite:ApplyInvite 写入邀请码并重置 status/existsUid",
|
||||
S4.Invite == "INVITE-CODE" && S4.Status == InviteStatus.UNKNOWN && S4.ExistsUid == null &&
|
||||
!S4.InviteAccepted
|
||||
)
|
||||
|
||||
val S5 = ReduceInvite(S4, AddFrags(1))
|
||||
Check("切片 Invite:无关动作不改状态", S5 == S4)
|
||||
|
||||
val Store = ReduxStore(S0, { S, A -> ReduceInvite(S, A) })
|
||||
var Notified = false
|
||||
Store.Subscribe(StoreListener { Notified = true })
|
||||
Store.Dispatch(SetInviteStatus("ALREADY_ACTIVATED", 7L))
|
||||
Check(
|
||||
"切片 Invite:接 Store 派发后状态已归约",
|
||||
Store.State.Status == InviteStatus.ALREADY_ACTIVATED && Store.State.ExistsUid == 7L && Notified
|
||||
)
|
||||
}
|
||||
|
||||
val Failed = Results.filter { !it.second }
|
||||
println("TankiOffline 状态层自检:${Results.size} 项")
|
||||
for ((Name, Ok) in Results) println((if (Ok) " ✓ " else " ✗ ") + Name)
|
||||
|
||||
@@ -9,22 +9,19 @@
|
||||
// ft/Ct = 派生值(selector)与组件订阅,S3 暂不移植(UI 层才需要)
|
||||
//
|
||||
// 这里的实现保持同样语义:
|
||||
// · 动作以 Kotlin 类为键(等价原包的构造函数作 Map key)
|
||||
// · 动作没有公共基类(原包同样没有),以 Kotlin 类作键(等价原包的构造函数作 Map key)
|
||||
// · 中间件顺序与原包一致:列表里靠后的中间件包在更外层
|
||||
// · 监听器在状态更新之后被通知;网关的按动作监听器在状态更新之前被通知
|
||||
package State
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/** 动作基类:与原包一致,动作是不可变数据类(Kotlin data class)。 */
|
||||
interface Action
|
||||
|
||||
/**
|
||||
* 原 module_10849 的 `ct`:`InitState(type, action)`。
|
||||
* 证据:`(p(ct).toString = ... "InitState(type=" + d(this.zap_1) + ", action=" + d(this.aaq_1) + ")")`。
|
||||
* 启动时用它把初始状态灌进 Store。
|
||||
*/
|
||||
data class InitState(val type: Any?, val action: Any?) : Action
|
||||
data class InitState(val type: Any?, val action: Any?)
|
||||
|
||||
/** Store 订阅者:状态变化后回调(对应原 `vt.maq_1` 里那些对象的 `kaq`)。 */
|
||||
fun interface StoreListener {
|
||||
@@ -33,7 +30,7 @@ fun interface StoreListener {
|
||||
|
||||
/** 中间件:签名等价原包的 `o(store, next) -> dispatch`。 */
|
||||
fun interface Middleware<S> {
|
||||
fun Invoke(Store: ReduxStore<S>, Next: (Action) -> Action): (Action) -> Action
|
||||
fun Invoke(Store: ReduxStore<S>, Next: (Any) -> Any): (Any) -> Any
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,18 +42,18 @@ fun interface Middleware<S> {
|
||||
*/
|
||||
class ReduxStore<S>(
|
||||
InitialState: S,
|
||||
private val Reducer: (S, Action) -> S,
|
||||
private val Reducer: (S, Any) -> S,
|
||||
Middlewares: List<Middleware<S>> = emptyList(),
|
||||
) {
|
||||
var State: S = InitialState
|
||||
private set
|
||||
|
||||
private val Listeners = ArrayList<StoreListener>()
|
||||
private val DispatchChain: (Action) -> Action
|
||||
private val DispatchChain: (Any) -> Any
|
||||
|
||||
init {
|
||||
// 原 `vt` 构造里的 invokeReducer:先归约,再逐个通知监听器,最后把动作原样返回
|
||||
var Chain: (Action) -> Action = { A ->
|
||||
var Chain: (Any) -> Any = { A ->
|
||||
State = Reducer(State, A)
|
||||
for (L in Listeners.toList()) L.OnStoreChanged()
|
||||
A
|
||||
@@ -65,7 +62,7 @@ class ReduxStore<S>(
|
||||
DispatchChain = Chain
|
||||
}
|
||||
|
||||
fun Dispatch(A: Action): Action = DispatchChain(A)
|
||||
fun Dispatch(A: Any): Any = DispatchChain(A)
|
||||
|
||||
fun Subscribe(L: StoreListener) {
|
||||
Listeners.add(L)
|
||||
@@ -82,7 +79,7 @@ class ReduxStore<S>(
|
||||
class ModelActionScope internal constructor(private val Gateway: ReduxGateway<*>) {
|
||||
internal val Removers = ArrayList<() -> Unit>()
|
||||
|
||||
fun Listen(Type: KClass<*>, Listener: (Action) -> Unit) {
|
||||
fun Listen(Type: KClass<*>, Listener: (Any) -> Unit) {
|
||||
Removers.add(Gateway.OnAction(Type, Listener))
|
||||
}
|
||||
}
|
||||
@@ -99,7 +96,7 @@ class ReduxGateway<S>(
|
||||
private val Store: ReduxStore<S>,
|
||||
private val Scheduler: (() -> Unit) -> Unit,
|
||||
) {
|
||||
private val ActionListeners = LinkedHashMap<KClass<*>, MutableList<(Action) -> Unit>>()
|
||||
private val ActionListeners = LinkedHashMap<KClass<*>, MutableList<(Any) -> Unit>>()
|
||||
private val ModelScopes = LinkedHashMap<Any, MutableList<() -> Unit>>()
|
||||
private val KeyScopes = LinkedHashMap<Any, MutableList<() -> Unit>>()
|
||||
private val GameObjectScopes = LinkedHashMap<Long, MutableList<() -> Unit>>()
|
||||
@@ -109,19 +106,19 @@ class ReduxGateway<S>(
|
||||
val StoreState: S get() = Store.State
|
||||
|
||||
/** 原 `uar`:把派发排进调度器(调度器负责错误隔离)。 */
|
||||
fun Dispatch(A: Action) {
|
||||
fun Dispatch(A: Any) {
|
||||
Scheduler { StoreDispatch(A) }
|
||||
}
|
||||
|
||||
/** 原 `gas` 返回的派发包装:先按动作类通知,再交给 Store。 */
|
||||
fun StoreDispatch(A: Action) {
|
||||
fun StoreDispatch(A: Any) {
|
||||
val List = ActionListeners[A::class]
|
||||
if (List != null) for (L in List.toList()) L(A)
|
||||
Store.Dispatch(A)
|
||||
}
|
||||
|
||||
/** 注册按动作类的监听器,返回摘除函数(原 `sar` / `tar`)。 */
|
||||
fun OnAction(Type: KClass<*>, Listener: (Action) -> Unit): () -> Unit {
|
||||
fun OnAction(Type: KClass<*>, Listener: (Any) -> Unit): () -> Unit {
|
||||
ActionListeners.getOrPut(Type) { ArrayList() }.add(Listener)
|
||||
return { ActionListeners[Type]?.remove(Listener) }
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user