FiveM State Bag Change Handler คืออะไร? วิธีใช้ AddStateBagChangeHandler
FiveM State Bag Change Handler คือระบบสำหรับตรวจจับการเปลี่ยนแปลงของ State Bag แบบ Event-driven โดยใช้ AddStateBagChangeHandler() ทำให้ Script สามารถทำงานทันทีเมื่อ State ของ Entity, Player หรือ State Bag ที่ตรงกับ Filter เปลี่ยน โดยไม่ต้องสร้าง CreateThread() เพื่อ Poll ค่าซ้ำ ๆ ตลอดเวลา
ตัวอย่างเช่น Server กำหนด State ของ Vehicle
Entity(vehicle).state:set(
'garage:locked',
true,
true
)
Client สามารถติดตามการเปลี่ยนแปลงของ Key นี้ด้วย
AddStateBagChangeHandler(
'garage:locked',
nil,
function(
bagName,
key,
value,
reserved,
replicated
)
print(
'Lock state:',
value
)
end
)
เมื่อ State เปลี่ยน Handler จะทำงาน
Concept คือ
State เปลี่ยน
↓
FiveM ตรวจพบ
↓
AddStateBagChangeHandler
↓
เรียก Handler
↓
Resource ตอบสนองต่อ State ใหม่
นี่ทำให้ State Bags ทรงพลังมากกว่าการเป็นเพียงพื้นที่เก็บ Key/Value เพราะ Resource สามารถสร้างระบบ Reactive ที่ทำงานเฉพาะเมื่อข้อมูลเปลี่ยนได้
① AddStateBagChangeHandler คืออะไร
AddStateBagChangeHandler() เป็น CFX Native สำหรับ Register Handler ที่ติดตามการเปลี่ยน State Bag
Syntax คือ
AddStateBagChangeHandler(
keyFilter,
bagFilter,
handler
)
ประกอบด้วย
keyFilter
→ สนใจ Key ไหน
bagFilter
→ สนใจ State Bag ไหน
handler
→ Function ที่ทำงานเมื่อ State เปลี่ยน
จึงสามารถ Filter ได้ทั้งตาม Key และ State Bag
② ตัวอย่างพื้นฐานที่สุด
สมมติต้องการติดตาม Key
isDead
สามารถเขียน
AddStateBagChangeHandler(
'isDead',
nil,
function(bagName, key, value)
print(
bagName,
key,
value
)
end
)
nil ที่ bagFilter หมายถึงไม่ได้จำกัด State Bag ใดเป็นพิเศษ
ดังนั้น Handler สนใจ Key isDead ที่ตรงกับ Filter
③ Callback มี Parameter อะไรบ้าง
Handler เต็มรูปแบบรับ
function(
bagName,
key,
value,
reserved,
replicated
)
แต่ละค่าคือ
bagName
→ State Bag ที่เปลี่ยน
key
→ Key ที่เปลี่ยน
value
→ ค่าใหม่
reserved
→ ปัจจุบันไม่ได้ใช้
replicated
→ การ Set นี้ถูกกำหนดให้ Replicate หรือไม่
Developer ไม่จำเป็นต้องใช้ทุก Parameter
④ bagName คืออะไร
bagName เป็น Internal State Bag ID
รูปแบบทั่วไปที่เอกสาร Cfx.re ระบุ เช่น
player:Source
entity:NetID
localEntity:Handle
ตัวอย่าง
player:15
อาจหมายถึง Player State Bag
หรือ
entity:81
หมายถึง Network Entity ที่เกี่ยวข้องกับ Network ID นั้น
แต่โดยทั่วไปไม่ควร Parse String เองถ้ามี Native สำหรับ Resolve Bag อยู่แล้ว
⑤ GetEntityFromStateBagName คืออะไร
ถ้า bagName เป็น Entity State Bag สามารถใช้
local entity =
GetEntityFromStateBagName(
bagName
)
Native นี้จะแปลง State Bag Name ไปเป็น Entity Handle ที่ใช้ได้ใน Context ปัจจุบัน
ตัวอย่าง
AddStateBagChangeHandler(
'fuel',
nil,
function(bagName, key, value)
local entity =
GetEntityFromStateBagName(
bagName
)
if entity == 0 then
return
end
print(
'Fuel:',
value
)
end
)
⑥ ทำไมต้องตรวจ entity == 0
เพราะ State Handler สามารถทำงานในจังหวะที่ Client ยังไม่มี Entity ที่ Valid หรือ Entity อาจออกจาก Scope ไปแล้ว
ดังนั้น Cfx.re Example เองใช้
if entity == 0 then
return
end
ก่อนทำ Entity Logic
อย่าสมมติว่า Handler ทำงานแล้ว Entity ต้องมีอยู่เสมอ
⑦ DoesEntityExist ยังควรตรวจไหม
ควรใน Logic ที่ Entity อาจหายระหว่างการรอ
เช่น
while not HasCollisionLoadedAroundEntity(
entity
) do
if not DoesEntityExist(entity) then
return
end
Wait(250)
end
เพราะระหว่าง Wait() Entity อาจออกจาก Scope หรือถูกลบ
⑧ GetPlayerFromStateBagName คืออะไร
สำหรับ Player State Bag ใช้
local player =
GetPlayerFromStateBagName(
bagName
)
ถ้า State Bag นั้นเป็นของ Player จะได้ Player Handle ที่เหมาะกับ Context
เอกสาร Cfx.re ระบุว่า
Server
→ คืน Player source
Client
→ คืน Player handle/index
ถ้า Bag ไม่ใช่ Player หรือ Player ไม่มีอยู่ จะคืน 0
⑨ ตัวอย่าง Player State Change
AddStateBagChangeHandler(
'isDead',
nil,
function(bagName, key, value)
local player =
GetPlayerFromStateBagName(
bagName
)
if player == 0 then
return
end
print(
'Player:',
player,
'Dead:',
value
)
end
)
เหมาะกับ State เช่น
isDead
onDuty
isCuffed
isBusy
ตาม Architecture ของ Resource
⑩ keyFilter คืออะไร
keyFilter ใช้จำกัดว่า Handler สนใจ State Key ใด
ตัวอย่าง
AddStateBagChangeHandler(
'vehicle:fuel',
nil,
function(...)
end
)
Handler นี้สนใจเฉพาะ
vehicle:fuel
แทนการรับ State Changes ทุก Key แล้วค่อยตรวจเองภายใน Function
⑪ keyFilter เป็น nil ได้ไหม
ได้
nil หมายถึงไม่มี Key Filter
Concept
AddStateBagChangeHandler(
nil,
nil,
function(...)
end
)
จะเป็น Handler กว้างมาก เพราะไม่ได้จำกัด Key หรือ Bag
ไม่ควรใช้ Global Listener แบบกว้างโดยไม่มีเหตุผล โดยเฉพาะ Server ใหญ่ที่ State เปลี่ยนบ่อย
⑫ ทำไมควรใช้ keyFilter
สมมติ Server มี State Keys จำนวนมาก
fuel
locked
engine
job
duty
cuffed
dead
mission
status
แต่ Resource ของคุณสนใจเพียง
fuel
ควร Filter ตั้งแต่ Registration
AddStateBagChangeHandler(
'fuel',
nil,
handler
)
ไม่จำเป็นต้องรับทุก Change แล้วเขียน if key ~= 'fuel' then return end ตลอด
⑬ bagFilter คืออะไร
bagFilter ใช้กรอง State Bag เฉพาะ Bag
ตัวอย่าง Internal Bag ID อาจเป็น
entity:125
ถ้าต้องการติดตาม State ของ Entity หนึ่งโดยเฉพาะ สามารถใช้ Bag Filter ที่ตรงกับ Entity State Bag นั้น
แต่ในระบบทั่วไป การ Filter ตาม Key แล้ว Resolve Entity ใน Handler มักยืดหยุ่นกว่า
⑭ bagFilter เป็น nil หมายถึงอะไร
หมายถึงไม่จำกัดว่า Key ต้องมาจาก Bag ไหน
ตัวอย่าง
AddStateBagChangeHandler(
'vehicle:fuel',
nil,
function(...)
end
)
หมายความว่า Resource สนใจการเปลี่ยน vehicle:fuel ของ State Bags ที่ตรงตามระบบ ไม่ได้ Fix เฉพาะ Vehicle คันเดียว
เหมาะกับ Vehicle Fuel System
⑮ value คืออะไร
value คือ ค่าใหม่ ที่กำลังถูก Set ให้ State Key
ตัวอย่าง
เดิม:
locked = false
เปลี่ยนเป็น:
locked = true
Handler จะได้รับ
value == true
จึงสามารถทำ
if value then
-- lock
else
-- unlock
end
⑯ ถ้าลบ State Key value เป็นอะไร
State Bag มักลบ Key ด้วยการ Set เป็น
nil
ดังนั้น Handler อาจได้รับ
value = nil
Resource ต้องออกแบบ Handler รองรับกรณี State ถูก Clear
ตัวอย่าง
if value == nil then
-- cleanup
return
end
⑰ ค่าเก่ายังอ่านได้ใน Handler หรือไม่
นี่เป็นรายละเอียดสำคัญ
เอกสาร Native ระบุว่า ระหว่าง Handler ทำงาน ค่าเก่ายังคงอยู่ใน State Bag
ส่วน Parameter
value
คือค่าใหม่
ดังนั้นใน Handler สามารถมี Concept
State Bag ปัจจุบัน
→ old value
value parameter
→ new value
นี่มีประโยชน์ถ้าต้องเปรียบเทียบก่อน/หลัง
⑱ ตัวอย่างอ่าน Old Value
Concept เช่น
AddStateBagChangeHandler(
'fuel',
nil,
function(bagName, key, value)
local oldValue =
GetStateBagValue(
bagName,
key
)
print(
'Old:',
oldValue,
'New:',
value
)
end
)
ต้องจำว่า Behavior นี้เป็นรายละเอียดของ Callback Timing
หลัง Handler จบ State จะเข้าสู่ค่าที่เปลี่ยนตามระบบ
⑲ reserved คืออะไร
Parameter ตัวที่ 4 คือ
reserved
เอกสารปัจจุบันระบุว่า
Currently unused
ดังนั้น Lua Script มักเขียน
function(
bagName,
key,
value,
_,
replicated
)
ใช้ _ เพื่อบอกว่าไม่สนใจ Parameter นี้
⑳ replicated คืออะไร
replicated บอกว่าการ Set State ครั้งนั้นถูกกำหนดให้ Replicate หรือไม่
เช่น Client อาจ Set
Entity(entity).state:set(
'taskAck',
true,
true
)
ค่า true สุดท้ายคือ Request ให้ Replicate
Handler สามารถดู replicated เพื่อใช้ประกอบ Logic ได้
แต่ไม่ควรตีความว่า replicated=true แปลว่าข้อมูลนั้น “เชื่อถือได้”
㉑ replicated=true ไม่ใช่ Security Proof
นี่สำคัญ
replicated = true
หมายถึง State Change ถูก Set ในรูปแบบ Replicated
ไม่ได้หมายถึง
ข้อมูลมาจาก Trusted Server
เสมอไป
เพราะตาม Default State Bag Policy Client Owner สามารถเขียน State บางประเภทได้
Security-critical Data ยังต้องมี Server Authority
㉒ Change Handler ปฏิเสธ State Change ได้ไหม
ไม่ได้ในปัจจุบัน
เอกสาร AddStateBagChangeHandler ระบุว่า Handler ไม่สามารถเลือก Reject State Change ได้
ดังนั้นอย่าเขียน Handler แล้วคิดว่า
if invalid then
return
end
จะยกเลิกการเปลี่ยน State
มันเพียงหยุด Logic ของ Handler
ไม่ใช่ Cancel State Change
㉓ ถ้าต้องป้องกัน Client แก้ State ทำอย่างไร
ต้องควบคุม Authority ตั้งแต่ State Write Policy
เช่น
Server เป็นผู้ Set State สำคัญ
Validate Client Requests
จำกัด Resource Architecture
ใช้
sv_stateBagStrictModeหากเหมาะกับ Server
เมื่อ Strict Mode เปิดตามเอกสารปัจจุบัน จะให้เฉพาะ Server แก้ State Bags ได้
ต้องตรวจ Compatibility ของ Resource ก่อนใช้งาน
㉔ Change Handler ใช้ Client ได้ไหม
ได้
Use Case ที่พบบ่อยมากคือ Server Set Entity State แล้ว Client ตอบสนอง
Server
↓
Set Entity State
↓
Replication
↓
Client Handler
↓
Native / Visual Action
เช่น
Lock Vehicle
Apply Vehicle Damage
Initialize Entity
Cuff Ped
Toggle Invincibility
Update NUI
㉕ Change Handler ใช้ Server ได้ไหม
ได้
State Bag Change Handler เป็น API ที่ใช้กับ State Awareness และสามารถมี Handler ฝั่ง Server ได้
ตัวอย่าง Server อาจติดตาม Player State
AddStateBagChangeHandler(
'job:onDuty',
nil,
function(
bagName,
key,
value,
reserved,
replicated
)
local src =
GetPlayerFromStateBagName(
bagName
)
if src == 0 then
return
end
print(
src,
value
)
end
)
แต่ State ที่ Client เขียนต้องถูกมองเป็น Untrusted Input ตาม Security Requirement
㉖ State Bag Handler ต่างจาก Event Handler อย่างไร
Event Handler
AddEventHandler(
'vehicle:stored',
function(...)
end
)
ทำงานเมื่อ Event ถูก Trigger
State Bag Change Handler
AddStateBagChangeHandler(
'vehicle:locked',
nil,
function(...)
end
)
ทำงานเมื่อ State เปลี่ยน
จำง่าย ๆ ว่า
Event Handler
→ ฟังเหตุการณ์
State Bag Handler
→ ฟังสถานะเปลี่ยน
㉗ State Bag Handler ต่างจาก Polling อย่างไร
Polling
CreateThread(function()
while true do
Wait(500)
local value =
Entity(vehicle).state.locked
-- check
end
end)
ต้องตรวจซ้ำแม้ค่าไม่เปลี่ยน
State Bag Handler
locked เปลี่ยน
↓
Handler ทำงาน
จึงเหมาะกับข้อมูลที่เปลี่ยนไม่บ่อย
㉘ Change Handler ช่วย Performance ได้อย่างไร
สมมติ Player อยู่ Server 4 ชั่วโมง
Polling ทุก 500 ms จะตรวจประมาณ
8 ครั้ง / 4 วินาที
ต่อเนื่องตลอด Session
แต่ถ้า Vehicle Lock เปลี่ยนเพียง 20 ครั้ง Handler ก็ทำงานเฉพาะตอนที่ State Change ถูกส่งมา
จึงลดงานซ้ำใน Use Case ที่เป็น Reactive State ได้มาก
อย่างไรก็ตาม Handler ที่ทำงานเองก็ต้อง Optimize เช่นกัน
㉙ Handler หนักเกินไปได้ไหม
ได้
ตัวอย่าง
AddStateBagChangeHandler(
'fuel',
nil,
function(...)
-- Query ใหญ่
-- Loop Entities หลายพันตัว
-- Heavy Native Calls
end
)
ถ้า State เปลี่ยนถี่ Handler ก็สามารถสร้าง Load ได้
Reactive ไม่ได้หมายความว่า Cost เป็นศูนย์
㉚ อย่า Update State ทุก Frame แล้วหวังว่า Handler จะ Optimize
ตัวอย่าง Server/Owner ทำ
CreateThread(function()
while true do
Wait(0)
Entity(vehicle).state:set(
'fuel',
fuel,
true
)
end
end)
แล้ว Client ใช้ Change Handler
Resource ก็ยังสร้าง State Updates ทุก Tick
สิ่งที่ควรทำคือ Set เมื่อค่าเปลี่ยนอย่างมีความหมาย
เช่น
100
↓
99
↓
98
ตาม Interval/Threshold ที่เหมาะสม
ไม่จำเป็นต้อง Replicateค่าเดิมซ้ำทุก Frame
㉛ ตัวอย่าง Vehicle Lock Handler
Server
Entity(vehicle).state:set(
'garage:locked',
true,
true
)
Client
AddStateBagChangeHandler(
'garage:locked',
nil,
function(bagName, key, value)
local vehicle =
GetEntityFromStateBagName(
bagName
)
if vehicle == 0 then
return
end
SetVehicleDoorsLocked(
vehicle,
value and 2 or 1
)
end
)
Server ควบคุม State ส่วน Client Apply Native Behavior
㉜ แต่ Vehicle Lock Security อยู่ตรงไหน
Client Native Lock เป็น Gameplay Presentation/Behavior
สิทธิ์ในการเปลี่ยนจาก
locked = true
เป็น
locked = false
ควรถูกตรวจ Server-side หากเกี่ยวข้องกับ Ownership
Flow ที่ดีกว่า
Client ขอปลดล็อก
↓
Server ตรวจ Vehicle Ownership/Key
↓
Server Set State
↓
Clients Handler Apply Lock
㉝ ตัวอย่าง Player Duty Handler
Server
Player(src).state:set(
'job:onDuty',
true,
true
)
Client Handler
AddStateBagChangeHandler(
'job:onDuty',
nil,
function(bagName, key, value)
local player =
GetPlayerFromStateBagName(
bagName
)
if player == 0 then
return
end
print(
'Duty changed:',
value
)
end
)
Resource UI สามารถใช้ Handler อัปเดต Icon หรือ Marker ได้โดยไม่ต้อง Poll Job State ตลอดเวลา
㉞ ตัวอย่าง Handcuff State
Server
Player(target).state:set(
'police:cuffed',
true,
true
)
Client Handler
AddStateBagChangeHandler(
'police:cuffed',
nil,
function(bagName, key, value)
local player =
GetPlayerFromStateBagName(
bagName
)
if player == 0 then
return
end
local ped =
GetPlayerPed(player)
if ped == 0 then
return
end
-- apply visual/gameplay state
end
)
Server ยังควรเป็น Authority ว่า Player ถูกจับจริงหรือไม่
㉟ State Bag Handler กับ Entity Scope
Handler อาจถูกเรียกในความสัมพันธ์กับ State Replication ของ Entity ที่เข้าสู่ Scope
ดังนั้น Client ต้องเข้าใจว่า
State Bag มีข้อมูล
ไม่ได้แปลว่า
Entity Handle พร้อมใช้เสมอทันที
นี่เป็นเหตุผลที่เอกสาร Cfx.re ใช้ GetEntityFromStateBagName() และตรวจ entity == 0
㊱ Entity อาจยังโหลด Collision ไม่เสร็จ
Official Example ของ AddStateBagChangeHandler() ใช้
while not HasCollisionLoadedAroundEntity(
entity
) do
if not DoesEntityExist(entity) then
return
end
Wait(250)
end
จากนั้นจึง Apply Native
เหมาะกับ State ที่ต้องทำงานกับ Entity ใน World หลัง Streaming พร้อม
㊲ ทำไมต้องเช็ก DoesEntityExist ระหว่าง Wait
เพราะขณะรอ Collision
Player เดินออกไป
↓
Entity ออกจาก Scope
↓
Local Entity หาย
ถ้า Thread ยังคงรอแล้วใช้ Entity Handle เดิม อาจเกิด Error/Behavior ผิด
จึงต้อง Check Lifecycle ระหว่าง Async Work
㊳ State Handler สามารถ Wait ได้ไหม
Lua Example ของ Cfx.re ใช้ Wait() ภายใน State Bag Handler ได้
เช่น
while not HasCollisionLoadedAroundEntity(
entity
) do
Wait(250)
end
แต่ต้องระวังว่า State อาจเปลี่ยนอีกรอบหรือ Entity Ownership/Scope เปลี่ยนระหว่างรอ
จึงต้อง Validate State อีกครั้งเมื่อจำเป็น
㊴ Ownership เปลี่ยนระหว่าง Handler ได้ไหม
ได้
โดยเฉพาะ Entity ที่เพิ่งสร้างและมี Clients หลายคนอยู่ใกล้
Cfx.re Migration Guide ระบุว่า Owner สามารถเปลี่ยนเร็วในช่วง Initialization
ดังนั้น Owner-dependent Handler ต้องตรวจ
NetworkGetEntityOwner(entity)
ในจังหวะที่กำลังทำ Owner-only Action จริง
㊵ ตัวอย่าง Owner-only Handler
AddStateBagChangeHandler(
'vehicle:needsInit',
nil,
function(bagName, key, value)
if not value then
return
end
local entity =
GetEntityFromStateBagName(
bagName
)
if entity == 0 then
return
end
if NetworkGetEntityOwner(
entity
) ~= PlayerId() then
return
end
SetVehicleOnGroundProperly(
entity
)
end
)
เหมาะเมื่อ Native ต้องให้ Current Entity Owner เป็นผู้ Apply
㊶ ทำไม Owner-only Handler แบบ Return ครั้งเดียวอาจยังไม่พอ
เพราะในช่วง Entity Initialization
Handler เรียก Client A
↓
A ไม่ใช่ Owner
↓
Return
↓
ต่อมา A/B Ownership เปลี่ยน
ถ้าไม่มี Mechanism เพิ่ม State อาจยังไม่ถูก Process
Cfx.re Migration Guide จึงแสดง Pattern ที่ State คงอยู่จน Owner คนใดคนหนึ่งทำงานสำเร็จและ Clear State
㊷ Pattern Initialization ที่ Robust
Server
Entity(vehicle).state:set(
'vehicle:needsInit',
true,
true
)
Client Handler
State = true
↓
Resolve Entity
↓
ตรวจ Current Owner
↓
ถ้ายังไม่ใช่
→ รอ/ลองตาม Lifecycle
ถ้าเป็น Owner
↓
Initialize
↓
Clear State
State ทำหน้าที่เป็น Pending Work Flag
㊸ GetStateBagValue คืออะไร
สามารถอ่าน Value จาก Bag โดยใช้
GetStateBagValue(
bagName,
key
)
มีประโยชน์ภายใน Handler โดยเฉพาะเมื่อต้องตรวจว่า State ยัง Pending อยู่หรือไม่หลัง Wait()
ตัวอย่าง
local current =
GetStateBagValue(
bagName,
key
)
if not current then
return
end
㊹ ทำไมต้องอ่าน State ใหม่หลัง Wait
สมมติ Handler ได้
value = true
จากนั้นรอ 1 วินาที
ระหว่างนั้น Resource อื่นอาจ Set
value = false
ดังนั้นก่อนทำ Action สำคัญหลัง Async Delay ควรพิจารณา Re-read State
ไม่ควรถือว่า Parameter ตอนเริ่ม Handler ยังเป็น Current State เสมอไป
㊺ State Handler ถูกเรียกหลายครั้งได้ไหม
ได้
ทุกครั้งที่ State Change ตรงกับ Filter Handler สามารถถูกเรียกอีกครั้ง
ตัวอย่าง
fuel = 100
fuel = 90
fuel = 80
fuel = 70
Handler fuel จะตอบสนองต่อการ Update เหล่านั้นตามระบบ
ดังนั้น Handler ต้องรองรับ Repeated Calls
㊻ ระวังสร้าง Thread ซ้ำใน Handler
ตัวอย่างอันตราย
AddStateBagChangeHandler(
'active',
nil,
function(bagName, key, value)
if not value then
return
end
CreateThread(function()
while true do
Wait(0)
-- permanent logic
end
end)
end
)
ถ้า State ถูก Set true หลายรอบ อาจสร้าง Infinite Threads หลายตัว
ควรมี Lifecycle/Guard ชัดเจน
㊼ Handler ควร Idempotent ไหม
หากทำได้ควรออกแบบให้ Calling ซ้ำไม่ทำให้ State พัง
ตัวอย่าง Set Vehicle Lock
SetVehicleDoorsLocked(
vehicle,
2
)
ทำซ้ำกับ State locked=true อาจไม่เสียหายมาก
แต่ Action อย่าง
Give Money
Give Item
Charge Account
ไม่ควรผูกกับ Client-side State Change Handler แบบไม่มี Transaction Protection
㊽ State Change Handler ใช้ทำ Reward ดีไหม
ไม่ควรใช้ Client Handler เป็น Authority ของ Economy
ตัวอย่างไม่ดี
State rewardReady = true
↓
Client Handler
↓
Client บอก Server Give Money
แล้ว Server เชื่อโดยไม่ตรวจ
Reward Eligibility ต้องอยู่ Server-side State/Business Logic
State Bag Handler ใช้เป็น Signal ได้ แต่ Server ยังต้อง Validate
㊾ Handler สามารถทำ NUI Update ได้ไหม
ได้ และเหมาะมาก
ตัวอย่าง Player Duty
job:onDuty เปลี่ยน
↓
Handler
↓
SendNUIMessage
↓
HUD Update
ไม่ต้องให้ HUD Poll Player State ทุก Frame
ตัวอย่าง
AddStateBagChangeHandler(
'job:onDuty',
nil,
function(bagName, key, value)
SendNUIMessage({
action = 'duty',
active = value
})
end
)
แต่ควร Filter ว่า State Bag เป็นของ Local Player หาก UI นี้ต้องแสดงเฉพาะตัวเอง
㊿ Local Player State Handler ต้อง Filter อย่างไร
แนวทางหนึ่งคือ Resolve Player จาก Bag แล้วตรวจว่าเป็น Local Player
local player =
GetPlayerFromStateBagName(
bagName
)
if player == 0 then
return
end
if player ~= PlayerId() then
return
end
จากนั้นค่อย Update Local HUD
ช่วยไม่ให้ State ของ Player คนอื่นเปลี่ยนแล้ว UI ของเราเปลี่ยนตาม
51 GlobalState Change ติดตามได้ไหม
State Bag Change Handler ถูกออกแบบสำหรับ State Bag Changes และ GlobalState ก็เป็น State Bag Type หนึ่งในระบบ
หาก Resource ต้องตอบสนองต่อ Global State Change สามารถออกแบบ Handler ตาม Bag/Key ที่เกี่ยวข้องได้
ตัวอย่าง Use Case
doubleXP
eventActive
weatherMode
serverPhase
แต่ต้อง Filter ให้แคบเพื่อไม่รับ Global Changes ที่ไม่เกี่ยวข้อง
52 Change Handler กับ playerEnteredScope
OneSync Documentation เตือนว่าการใช้
playerEnteredScope
playerLeftScope
มี Scaling Cost ตามจำนวน Players ใน Scope
Cfx.re แนะนำ State Bags เมื่อ Use Case สามารถใช้ Scoped State Change แทนได้
ดังนั้นระบบ Player State หลายประเภทควรพิจารณา
Player State
+
AddStateBagChangeHandler
แทนการ Manage Scope Events เอง
53 ทำไม State Bag Scale ดีกว่าใน Use Case นี้
แทน
Player A เข้า Scope
↓
Event
Player B เข้า Scope
↓
Event
Player C เข้า Scope
↓
Event
และ Resource ต้อง Sync State เอง
State Bag สามารถให้ Relevant State ตาม State Awareness/Replication ของ Entity/Player
ช่วยลด Custom Scope Management
แต่ไม่ใช่ว่า State Bag จะเหมาะกับทุก Event
54 AddStateBagChangeHandler Return ค่าอะไร
Native นี้ Return ค่า Integer ซึ่งใช้เป็น Handler Cookie
Concept
local cookie =
AddStateBagChangeHandler(
'fuel',
nil,
function(...)
end
)
Cookie นี้สามารถใช้ระบุ Handler หากต้องการ Remove Handler
55 RemoveStateBagChangeHandler คืออะไร
FiveM มี
RemoveStateBagChangeHandler(
cookie
)
สำหรับลบ State Bag Change Handler ที่ Register ไว้
ตัวอย่าง
local cookie =
AddStateBagChangeHandler(
'fuel',
nil,
handler
)
-- เมื่อไม่ต้องการแล้ว
RemoveStateBagChangeHandler(
cookie
)
อย่างไรก็ตาม เอกสาร Cfx.re ปัจจุบันทำเครื่องหมาย RemoveStateBagChangeHandler ว่า Experimental
จึงควรตรวจ Runtime Compatibility ก่อนพึ่ง API นี้ใน Production Logic สำคัญ
56 เมื่อไรควร Remove Handler
Handler ส่วนใหญ่ที่ต้องทำงานตลอด Resource Lifetime ไม่จำเป็นต้องสร้าง/ลบซ้ำตลอดเวลา
แต่ Dynamic Handler อาจเหมาะกับการ Remove เช่น
Temporary Mission
Debug System
Temporary Resource Feature
Runtime Module Enable/Disable
อย่าสร้าง Handler ใหม่ทุกครั้งที่เปิด Menu ถ้า Handler ถาวรตัวเดียวก็ทำงานได้
57 State Handler ซ้ำมีผลอะไร
ถ้า Register Handler เดิมหลายครั้ง
Handler 1
Handler 2
Handler 3
เมื่อ State เปลี่ยน Logic อาจทำงานซ้ำหลายรอบ
เช่น NUI Update ซ้ำ, Native Apply ซ้ำ หรือ Thread ถูกสร้างซ้ำ
ดังนั้นควรวาง Registration ใน Initialization ที่ชัดเจน
58 วิธี Debug AddStateBagChangeHandler
เริ่มจาก Handler ที่ Print ทุก Parameter ที่ต้องการ
AddStateBagChangeHandler(
'debug:test',
nil,
function(
bagName,
key,
value,
reserved,
replicated
)
print(
'bag:',
bagName
)
print(
'key:',
key
)
print(
'value:',
value
)
print(
'replicated:',
replicated
)
end
)
จากนั้นค่อยเพิ่ม
GetEntityFromStateBagName()
หรือ
GetPlayerFromStateBagName()
เพื่อหาว่า Bag เป็นของอะไร
59 Checklist ก่อนใช้ State Bag Change Handler
ตรวจอย่างน้อย
① State นี้ควรใช้ Handler แทน Polling หรือไม่?
② keyFilter แคบพอหรือไม่?
③ ต้องใช้ bagFilter หรือไม่?
④ bagName เป็น Entity, Player หรือ Global?
⑤ Resolve ด้วย Native ที่ถูกต้องหรือไม่?
⑥ ตรวจ entity/player == 0 หรือยัง?
⑦ Handler อาจถูกเรียกหลายครั้งหรือไม่?
⑧ Handler ทำ Heavy Logic หรือไม่?
⑨ Handler มี Wait หรือ Async หรือไม่?
⑩ หลัง Wait ต้องอ่าน State ใหม่ไหม?
⑪ Entity อาจออกจาก Scope หรือไม่?
⑫ ต้องตรวจ DoesEntityExist ซ้ำไหม?
⑬ Ownership เปลี่ยนได้ไหม?
⑭ Logic ต้องทำเฉพาะ Owner หรือไม่?
⑮ value=nil ถูก Handle หรือยัง?
⑯ State นี้ Client เขียนได้หรือไม่?
⑰ Security-critical หรือไม่?
⑱ replicated ถูกตีความถูกหรือไม่?
⑲ Handler ถูก Register ซ้ำหรือไม่?
⑳ ต้อง Remove Handler จริงไหม?
⑥⓪ ตัวอย่าง State Bag Change Handler แบบสมบูรณ์
สมมติ Server สร้าง Vehicle แล้วต้องการให้ Client Owner ทำ Initialization
server.lua
local function createMissionVehicle(
model,
coords
)
local vehicle =
CreateVehicleServerSetter(
joaat(model),
'automobile',
coords.x,
coords.y,
coords.z,
coords.w
)
if vehicle == 0 then
return nil
end
SetEntityOrphanMode(
vehicle,
2
)
Entity(vehicle).state:set(
'mission:needsInit',
true,
true
)
return vehicle
end
client.lua
AddStateBagChangeHandler(
'mission:needsInit',
nil,
function(
bagName,
key,
value,
reserved,
replicated
)
if not value then
return
end
local timeout =
GetGameTimer() + 5000
while true do
Wait(0)
local currentState =
GetStateBagValue(
bagName,
key
)
if not currentState then
return
end
local entity =
GetEntityFromStateBagName(
bagName
)
if entity ~= 0
and DoesEntityExist(entity)
and NetworkGetEntityOwner(
entity
) == PlayerId() then
SetVehicleOnGroundProperly(
entity
)
Entity(entity).state:set(
'mission:needsInit',
nil,
true
)
return
end
if GetGameTimer() >
timeout then
return
end
end
end
)
Flow คือ
Server
↓
Create Networked Vehicle
↓
needsInit = true
↓
State Replication
↓
Client Handler
↓
Resolve Entity
↓
ตรวจ Current Owner
↓
Owner ทำ Initialization
↓
Clear State
นี่เป็นตัวอย่างสำคัญของ State Bag Change Handler เพราะไม่ผูก Initialization ไว้กับ Client คนใดคนหนึ่งถาวร
คำถามที่พบบ่อยเกี่ยวกับ FiveM State Bag Change Handler
FiveM State Bag Change Handler คืออะไร
คือ Handler ที่ทำงานเมื่อ State Bag Key ที่ตรงกับ Filter เปลี่ยน
ใช้ Function อะไร
ใช้
AddStateBagChangeHandler(
keyFilter,
bagFilter,
handler
)
keyFilter คืออะไร
ใช้จำกัด State Key ที่ต้องการติดตาม
bagFilter คืออะไร
ใช้จำกัด State Bag ที่ต้องการติดตาม
ใส่ nil ที่ bagFilter ได้ไหม
ได้ หมายถึงไม่จำกัด Bag เฉพาะตัว
Handler รับค่าอะไร
หลัก ๆ คือ
bagName
key
value
reserved
replicated
value เป็นค่าเก่าหรือค่าใหม่
value คือค่าใหม่ ส่วนเอกสาร Native ระบุว่าค่าเก่ายังอยู่ใน State Bag ระหว่าง Callback ทำงาน
reserved คืออะไร
ปัจจุบันไม่ได้ใช้งาน
replicated คืออะไร
บอกว่า State Set นั้นถูกกำหนดให้ Replicate หรือไม่
replicated=true แปลว่ามาจาก Serverไหม
ไม่ควรตีความแบบนั้น มันไม่ได้เป็น Security Proof
Handler ยกเลิก State Change ได้ไหม
ไม่ได้ในปัจจุบัน เอกสารระบุว่า Change Handler ไม่สามารถ Reject Change ได้
วิธีหา Entity จาก bagName
ใช้
GetEntityFromStateBagName(
bagName
)
วิธีหา Player จาก bagName
ใช้
GetPlayerFromStateBagName(
bagName
)
GetEntityFromStateBagName คืน 0 ได้ไหม
ได้ หากไม่มี Entity ที่ Valid ใน Context ปัจจุบัน
Handler ใช้ Wait ได้ไหม
ได้ใน Lua Coroutine Context ตามตัวอย่าง Cfx.re แต่ต้องคำนึงว่า Entity/State/Ownership อาจเปลี่ยนระหว่างรอ
Handler ใช้แทน CreateThread ได้ไหม
ใช้แทน Polling ได้ดีเมื่อ Requirement คือทำงานตอน State เปลี่ยน แต่ไม่ได้แทน Thread ที่ต้อง Render หรือทำงานทุก Frame
Handler ใช้กับ Vehicle Fuel ได้ไหม
ได้
Handler ใช้กับ Player Duty ได้ไหม
ได้
Handler ใช้กับ Handcuff ได้ไหม
ได้ ถ้า Architecture ของ State/Authority ถูกต้อง
Handler ปลอดภัยจาก Client Cheat ไหม
ไม่โดยอัตโนมัติ Security ขึ้นกับว่าใครสามารถเขียน State และ Server ตรวจ Authority อย่างไร
Handler ถูกเรียกหลายครั้งได้ไหม
ได้เมื่อ State เปลี่ยนหลายครั้ง
AddStateBagChangeHandler Return อะไร
Return Handler Cookie แบบ Integer
ลบ Handler ได้ไหม
มี RemoveStateBagChangeHandler(cookie) แต่เอกสาร Cfx.re ปัจจุบันระบุ Native นี้เป็น Experimental
สรุป FiveM State Bag Change Handler คืออะไร
FiveM State Bag Change Handler คือระบบ Reactive สำหรับทำงานทันทีเมื่อ State Bag เปลี่ยน โดยใช้ AddStateBagChangeHandler()
โครงสร้างพื้นฐานคือ
AddStateBagChangeHandler(
'state:key',
nil,
function(
bagName,
key,
value,
reserved,
replicated
)
-- react to change
end
)
แนวคิดสำคัญคือ
Polling
=
ถามซ้ำว่า State เปลี่ยนหรือยัง
แต่
State Bag Change Handler
=
State เปลี่ยนแล้ว FiveM เรียกเรา
จึงเหมาะกับข้อมูลอย่าง
Vehicle Lock
Fuel
Player Duty
Cuffed State
Entity Initialization
Mission State
HUD State
โดยเฉพาะ State ที่เปลี่ยนไม่บ่อยและไม่จำเป็นต้องตรวจทุก Frame
แต่ต้องจำรายละเอียดสำคัญหลายข้อ ได้แก่
bagName
→ ใช้ Resolve Entity/Player
value
→ ค่าใหม่
State Bag ระหว่าง Callback
→ ยังสามารถมีค่าเก่า
entity == 0
→ ต้อง Handle
replicated
→ ไม่ใช่ Security Proof
Handler
→ Reject State Change ไม่ได้
Ownership
→ สามารถเปลี่ยนระหว่าง Async Logic
สำหรับผู้ที่เรียน FiveM Developer กับ comsiam สิ่งสำคัญคืออย่าเปลี่ยนทุก CreateThread() ให้เป็น State Handler แบบไม่คิด แต่ให้เลือกตามลักษณะของข้อมูล
ถ้าต้อง Draw Marker ทุก Frame ยังต้องใช้ Thread แต่ถ้าต้องรู้เพียงว่า locked เปลี่ยนจาก false เป็น true เมื่อไร AddStateBagChangeHandler() มักเหมาะกว่าการ Poll ทุก 500 ms
หลักจาก comsiam ที่ควรจำคือ State Bag ใช้เก็บสถานะ ส่วน State Bag Change Handler ใช้ตอบสนองเมื่อสถานะนั้นเปลี่ยน เมื่อใช้สองอย่างนี้คู่กันอย่างถูกต้อง Resource จะมีโครงสร้างแบบ Event-driven มากขึ้นและลดงานซ้ำที่ไม่จำเป็นได้มาก
หัวข้อถัดไปคือ FiveM Routing Bucket คืออะไร ซึ่งจะเข้าสู่ระบบ Instance/Dimension ของ OneSync ว่า Player และ Entity ถูกแยกโลกกันอย่างไร และทำไม Routing Bucket จึงสำคัญกับ Character Selection, Mission Instance, Lobby และ Interior System
Comments
Post a Comment