Understanding component-based architecture in Mythic Framework
Components are self-contained units of functionality that encapsulate related methods and data. They register with the framework via RegisterComponent, expose a public API, and can depend on or extend other components.
local MyComponent = { -- Private data (underscore convention) _data = {}, -- Public methods (always use self parameter) DoSomething = function(self, param) -- Method implementation return result end, -- Internal helper (underscore = private by convention) _helperMethod = function(self) -- Not intended for external use end, -- Component metadata _protected = false, -- Prevent other resources from overriding? _required = {}, -- Method names that must exist _name = 'mycomponent' -- Name used in logging}-- Register with frameworkexports['mythic-base']:RegisterComponent('MyComponent', MyComponent)
All public methods use function(self, ...) and are called with colon syntax: Component:Method(args). The self parameter is automatic with : calls.
Registered by feature resources (mythic-inventory, mythic-jobs, etc.) to expose their APIs:
-- These are accessed via COMPONENTS after RetrieveComponents()Inventory:AddItem(owner, 'water', 5)Jobs.Permissions:HasJob(source, 'police')Vehicles.Owned:GetPlayerVehicles(source)Phone.Notification:Add(source, 'Title', 'Description', os.time(), 5000, 'phone')
Components use namespacing with dots to organize related functionality:
Use RequestDependencies to ensure required components are loaded first:
-- mythic-shop/server/component.luaAddEventHandler('Core:Shared:Ready', function() exports['mythic-base']:RequestDependencies('Shop', { 'Inventory', 'Fetch', 'Logger' }, function(errors) if #errors > 0 then return end -- All dependencies loaded, safe to use RetrieveComponents() RegisterCallbacks() Startup() end)end)-- After RetrieveComponents(), components are available as localsfunction RegisterCallbacks() Callbacks:RegisterServerCallback('mythic-shop:Purchase', function(source, data, cb) local char = Fetch:Source(source):GetData('Character') -- Purchase logic... cb({ success = true }) end)end
-- Component triggers event for other systems to reactexports['mythic-base']:RegisterComponent('Shop', { Purchase = function(self, player, item, price) local success = self:_processPurchase(player, item, price) if success then TriggerEvent('Shop:PurchaseComplete', player, item, price) end return success end})-- Other components listenAddEventHandler('Shop:PurchaseComplete', function(player, item, price) Logger:Info('Shop', 'Purchase complete', { player = player, item = item })end)
Never assume components are available immediately:
-- Bad: may be nil if component hasn't loaded yetlocal Inventory = Inventory-- Good: wait for dependenciesexports['mythic-base']:RequestDependencies('MyResource', { 'Inventory' }, function(errors) if #errors == 0 then RetrieveComponents() endend)
Error Handling
Always handle errors gracefully:
exports['mythic-base']:RegisterComponent('Payment', { Charge = function(self, player, amount) if not player or amount <= 0 then return false, 'Invalid parameters' end local balance = self:GetBalance(player) if balance < amount then return false, 'Insufficient funds' end local success = self:_deductBalance(player, amount) if not success then Logger:Error('Payment', 'Failed to deduct balance') return false, 'Transaction failed' end return true end})
Use Protected for Core Components
Prevent accidental overrides of critical components:
exports['mythic-base']:RegisterComponent('Inventory', { _protected = true, -- Methods cannot be overridden by other resources})