quad/quad-base/src/Relate.luau
qwreey d9898d6629
feat(m2): 첫 단위 공통 기반 — Void/Brand/LifetimeHandle/Ref 최소형 + mock 생명주기 + spec 5개
- quad-base/src: Void.luau(단일 no-op), Brand.luau(Brand() + 브랜드 인스턴스 15 +
  M2 is* 11), LifetimeHandle.luau(InitLifetimeHandle — 모듈 인스턴스에 영어 level 2
  에러 스텁 4종), Ref.luau(.Value/.Revision/:Set/:Callback/:WeakCallback/:Uncallback,
  EpochBrand+RefBrand), init.luau 재export. Relate.luau는 타입만 quad-types에서 재export.
- quad-types: Quad에 M2 첫 단위 탑레벨 값 + Ref<T>/RefCallback<T>/Relate/Epoch 타입.
- test/mock.luau: installLifetime(quad) — lifecycle-pattern.md (0)/(1) 스케치 그대로,
  Destroy가 모든 Connection을 끊도록 보강(gcconn 판정 근거).
- scripts/test.sh: spec.* 수집 + luau-analyze(src/spec/mock). 전부 ALL PASS, analyze 0건.
- 발견 ①: H-165 pesde shim은 생성 시점 export 타입만 안다(project-setup-plan.md),
  H-166 Ref.Revision 초기값 0(ref-plan.md). ROADMAP 공통 기반 체크박스 완료 표기.

Co-authored-by: qwreey <me@qwreey.moe>
2026-08-28 18:10:50 +09:00

90 lines
2.1 KiB
Text

--[[
Relate — inst를 weak 키로 하는 범용 릴레이션 프리미티브.
`.claude/base/relate-plan.md` "API"/"실제 구조" 절 그대로 구현.
]]
local QuadTypes = require("../roblox_packages/quad_types")
-- 타입의 소스는 `quad-types`(`Quad.Relate` 필드가 같은 타입을 써야 함) — 여기선 재export만.
export type Relate = QuadTypes.Relate
type Bucket = {
StrongMap: { [any]: any }?,
WeakMap: { [any]: any }?,
}
-- 모든 WeakMap이 공유하는 단일 메타테이블 — 매번 새로 만들 이유가 없음
local WEAK_VALUE_MT = { __mode = "v" }
local RelateImpl = {}
RelateImpl.__index = RelateImpl
local function getBucket(self, inst: any): Bucket?
return self.buckets[inst]
end
local function getOrCreateBucket(self, inst: any): Bucket
local bucket = self.buckets[inst]
if bucket == nil then
bucket = {} :: Bucket
self.buckets[inst] = bucket
end
return bucket
end
function RelateImpl:SetStrong(inst: any, key: any, value: any)
local bucket = getOrCreateBucket(self, inst)
local strongMap = bucket.StrongMap
if strongMap == nil then
local newMap = {}
bucket.StrongMap = newMap
newMap[key] = value
return
end
strongMap[key] = value
end
function RelateImpl:GetStrong(inst: any, key: any): any?
local bucket = getBucket(self, inst)
if bucket == nil then
return nil
end
local strongMap = bucket.StrongMap
if strongMap == nil then
return nil
end
return strongMap[key]
end
function RelateImpl:SetWeak(inst: any, key: any, value: any)
local bucket = getOrCreateBucket(self, inst)
local weakMap = bucket.WeakMap
if weakMap == nil then
local newMap = setmetatable({}, WEAK_VALUE_MT)
bucket.WeakMap = newMap
newMap[key] = value
return
end
weakMap[key] = value
end
function RelateImpl:GetWeak(inst: any, key: any): any?
local bucket = getBucket(self, inst)
if bucket == nil then
return nil
end
local weakMap = bucket.WeakMap
if weakMap == nil then
return nil
end
return weakMap[key]
end
local function Relate(): Relate
local self = setmetatable({
buckets = setmetatable({}, { __mode = "k" }) :: { [any]: Bucket },
}, RelateImpl)
return (self :: any) :: Relate
end
return Relate