Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions Modules/Config/AttributeConfig.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
--!strict
--[[
AttributeConfig - インスタンスのAttributeを使用した実装

Roblox インスタンスのAttribute機能に対応
後方互換性のために維持
]]

local BaseConfig = require(script.Parent.BaseConfig)
local Types = require(script.Parent.Type)

type ConfigValue = Types.ConfigValue
type ConfigSchema = Types.ConfigSchema

local AttributeConfig = {}
AttributeConfig.__index = AttributeConfig
setmetatable(AttributeConfig, { __index = BaseConfig })

export type AttributeConfig = BaseConfig.BaseConfig & {
Instance: Instance,
}

--[[
新しいAttributeConfigインスタンスを作成

@param instance - Attributeを持つインスタンス
@param schema - 設定スキーマ(オプション)
@return AttributeConfig
]]
function AttributeConfig.new(
instance: Instance,
schema: ConfigSchema?
): AttributeConfig
local self = BaseConfig._new("Attribute", schema) :: any
setmetatable(self, AttributeConfig)

self.Instance = instance

-- 初期値をロード
self:_loadValues()

return self
end

--[[
インスタンスからAttributeの値を読み込み
]]
function AttributeConfig:_loadValues(): ()
local attributes = self.Instance:GetAttributes()

for key, value in pairs(attributes) do
-- スキーマがある場合はバリデーション
if self.Schema then
local definition = self.Schema[key]
if definition then
if self:_validateType(key, value) then
self:_setValue(key, value)
else
warn(string.format(
"[AttributeConfig] Type mismatch for key '%s': expected %s, got %s",
key,
definition.Type or typeof(definition.Default),
typeof(value)
))
self:_setValue(key, definition.Default)
end
else
-- スキーマにないキーもロード
self:_setValue(key, value)
end
else
self:_setValue(key, value)
end
end

-- デフォルト値を適用(スキーマにあるがAttributeにないキー)
self:_applyDefaults()
end

--[[
変更監視を開始

@return RBXScriptSignal - 変更イベント
]]
function AttributeConfig:Observe(): RBXScriptSignal
local event = BaseConfig.Observe(self)

-- AttributeChangedは全属性の変更を一つのイベントで受け取る
if self.Connections._attributeChanged == nil then
self.Connections._attributeChanged = self.Instance.AttributeChanged:Connect(function(attributeName: string)
-- スキーマがある場合は監視対象かチェック
if not self:_shouldObserve(attributeName) then
return
end

local newValue = self.Instance:GetAttribute(attributeName)
if newValue ~= nil and self:_validateType(attributeName, newValue) then
self:_setValue(attributeName, newValue)
end
end)
end

return event
end

--[[
最新値に更新
]]
function AttributeConfig:Refresh(): ()
self:_loadValues()
end

--[[
Attributeの値を更新(双方向バインディング用)

@param key - キー名
@param value - 新しい値
@return boolean - 成功したかどうか
]]
function AttributeConfig:Set(key: string, value: ConfigValue): boolean
local success = pcall(function()
self.Instance:SetAttribute(key, value)
end)

if success then
self:_setValue(key, value)
end

return success
end

return AttributeConfig
192 changes: 192 additions & 0 deletions Modules/Config/BaseConfig.luau
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
--!strict
--[[
BaseConfig - 全てのConfig実装の基底クラス

共通のインターフェースと基本実装を提供する抽象クラス
具象クラスはこれを継承して各ソースタイプ固有の実装を行う
]]

local Types = require(script.Parent.Type)

export type ConfigValue = Types.ConfigValue
export type ConfigSchema = Types.ConfigSchema
export type ConfigSourceType = Types.ConfigSourceType
export type ConfigErrorState = Types.ConfigErrorState
export type ConnectionMap = Types.ConnectionMap

local BaseConfig = {}
BaseConfig.__index = BaseConfig

export type BaseConfig = {
-- プロパティ
Source: ConfigSourceType,
Schema: ConfigSchema?,
Error: ConfigErrorState?,
Values: { [string]: ConfigValue },
Connections: ConnectionMap,
Observer: BindableEvent?,

-- 動的キーアクセス
[string]: any,
}

--[[
新しいBaseConfigインスタンスを作成(サブクラス用)

@param source - 設定ソースの種類
@param schema - 設定スキーマ(オプション)
@return BaseConfig
]]
function BaseConfig._new(source: ConfigSourceType, schema: ConfigSchema?): BaseConfig
local self = setmetatable({}, BaseConfig) :: any
self.Source = source
self.Schema = schema
self.Error = "None"
self.Values = {}
self.Connections = {}
self.Observer = nil
return self
end

--[[
スキーマからデフォルト値を適用
]]
function BaseConfig:_applyDefaults(): ()
if self.Schema == nil then
return
end

for key, definition in pairs(self.Schema) do
if self.Values[key] == nil then
self.Values[key] = definition.Default
end
end
end

--[[
値の型をバリデーション

@param key - キー名
@param value - バリデーションする値
@return boolean - バリデーション成功かどうか
]]
function BaseConfig:_validateType(key: string, value: any): boolean
if self.Schema == nil then
return true
end

local definition = self.Schema[key]
if definition == nil then
return true
end

local expectedType = definition.Type
if expectedType == nil then
-- Defaultから型を推論
expectedType = typeof(definition.Default) :: Types.ConfigValueType
end

return typeof(value) == expectedType
end

--[[
キーが監視対象かどうかを判定

@param key - キー名
@return boolean
]]
function BaseConfig:_shouldObserve(key: string): boolean
if self.Schema == nil then
return true -- スキーマがなければ全て監視
end

local definition = self.Schema[key]
if definition == nil then
return false
end

return definition.Observe == true
end

--[[
値を設定し、必要に応じて変更を通知

@param key - キー名
@param value - 新しい値
]]
function BaseConfig:_setValue(key: string, value: ConfigValue): ()
local oldValue = self.Values[key]
self.Values[key] = value
self[key] = value

if self.Observer and oldValue ~= value then
self.Observer:Fire(key, value, oldValue)
end
end

--[[
値を取得

@param key - キー名
@return ConfigValue? - 値(存在しない場合はnil)
]]
function BaseConfig:Get(key: string): ConfigValue?
return self.Values[key]
end

--[[
デフォルト値付きで値を取得

@param key - キー名
@param default - デフォルト値
@return ConfigValue
]]
function BaseConfig:GetWithDefault(key: string, default: ConfigValue): ConfigValue
local value = self.Values[key]
if value == nil then
return default
end
return value
end

--[[
変更監視を開始(サブクラスでオーバーライド)

@return RBXScriptSignal - 変更イベント
]]
function BaseConfig:Observe(): RBXScriptSignal
if self.Observer == nil then
self.Observer = Instance.new("BindableEvent")
end
return self.Observer.Event
end

--[[
最新値に更新(サブクラスでオーバーライド)
]]
function BaseConfig:Refresh(): ()
-- サブクラスで実装
end

--[[
リソースを解放
]]
function BaseConfig:Destroy(): ()
-- コネクションを切断
for key, connection in pairs(self.Connections) do
connection:Disconnect()
self.Connections[key] = nil
end

-- Observerを破棄
if self.Observer then
self.Observer:Destroy()
self.Observer = nil
end

-- テーブルをクリア
table.clear(self.Values)
table.clear(self)
end

return BaseConfig
Loading