1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace ManiaLive\PluginHandler;
13:
14: use ManiaLive\Event\Dispatcher;
15: use ManiaLive\Application\Listener as AppListener;
16: use ManiaLive\Application\Event as AppEvent;
17: use ManiaLive\Application\ErrorHandling;
18: use ManiaLive\Data\Storage;
19: use ManiaLive\DedicatedApi\Callback\Listener as ServerListener;
20: use ManiaLive\DedicatedApi\Callback\Event as ServerEvent;
21: use DedicatedApi\Structures\Status;
22: use ManiaLive\Utilities\Console;
23:
24: 25: 26: 27:
28: final class PluginHandler extends \ManiaLib\Utils\Singleton implements AppListener, ServerListener
29: {
30: private $loadedPlugins = array();
31: private $delayedPlugins = array();
32:
33: protected function __construct()
34: {
35: Dispatcher::register(AppEvent::getClass(), $this, AppEvent::ON_INIT | AppEvent::ON_TERMINATE);
36: Dispatcher::register(ServerEvent::getClass(), $this, ServerEvent::ON_SERVER_START | ServerEvent::ON_SERVER_STOP);
37: }
38:
39: function load($pluginId)
40: {
41: try
42: {
43: if( !($plugin = $this->register($pluginId)) )
44: return false;
45:
46: $this->prepare($plugin);
47: $plugin->onReady();
48: return true;
49: }
50: catch(\Exception $e)
51: {
52: $this->unload($pluginId);
53: ErrorHandling::processRuntimeException($e);
54: return false;
55: }
56: }
57:
58: 59: 60: 61: 62:
63: function isLoaded($pluginId, $min = Dependency::NO_LIMIT, $max = Dependency::NO_LIMIT)
64: {
65: return isset($this->loadedPlugins[$pluginId])
66: && ($min == Dependency::NO_LIMIT || version_compare($this->loadedPlugins[$pluginId]->getVersion(), $min) >= 0)
67: && ($max == Dependency::NO_LIMIT || version_compare($this->loadedPlugins[$pluginId]->getVersion(), $max) <= 0);
68: }
69:
70: 71: 72:
73: function getLoadedPluginsList()
74: {
75: return array_keys($this->loadedPlugins);
76: }
77:
78: function unload($pluginId)
79: {
80: if(isset($this->loadedPlugins[$pluginId]))
81: {
82: foreach($this->loadedPlugins as $plugin)
83: foreach($plugin->getDependencies() as $dependency)
84: if($dependency->getPluginId() == $pluginId)
85: throw new Exception('Plugin "'.$pluginId.'" cannot be unloaded. It is required by other plugins.');
86:
87: $this->loadedPlugins[$pluginId]->onUnload();
88: unset($this->loadedPlugins[$pluginId]);
89:
90: Dispatcher::dispatch(new Event(Event::ON_PLUGIN_UNLOADED, $pluginId));
91: }
92: else if(isset($this->delayedPlugins[$pluginId]))
93: unset($this->delayedPlugins[$pluginId]);
94: }
95:
96: private function register($pluginId)
97: {
98: if(isset($this->loadedPlugins[$pluginId]) || isset($this->delayedPlugins[$pluginId]))
99: throw new Exception('Plugin "'.$pluginId.'" cannot be loaded, maybe there is a naming conflict!');
100:
101: $parts = explode('\\', $pluginId);
102: $className = '\\ManiaLivePlugins\\'.$pluginId.'\\'.end($parts);
103: if(!class_exists($className))
104: {
105: $className = '\\ManiaLivePlugins\\'.$pluginId.'\\Plugin';
106: if(!class_exists($className))
107: throw new Exception('Plugin "'.$pluginId.'" not found!');
108: }
109:
110: $plugin = new $className();
111: $plugin->onInit();
112: if(Storage::getInstance()->serverStatus->code > Status::LAUNCHING || $plugin instanceof WaitingCompliant)
113: {
114: Console::println('[PluginHandler] Loading plugin "'.$pluginId.'"...');
115: return $this->loadedPlugins[$pluginId] = $plugin;
116: }
117: Console::println('[PluginHandler] Server is waiting, plugin "'.$pluginId.'" will be loaded later...');
118: $this->delayedPlugins[$pluginId] = $plugin;
119: return null;
120: }
121:
122: private function prepare($plugin)
123: {
124: $this->checkDependencies($plugin);
125: $plugin->onLoad();
126: Dispatcher::dispatch(new Event(Event::ON_PLUGIN_LOADED, $plugin->getId()));
127: }
128:
129: private function checkDependencies($plugin)
130: {
131: foreach($plugin->getDependencies() as $dependency)
132: {
133:
134: $name = $dependency->getPluginId();
135: if(isset($this->loadedPlugins[$name]))
136: {
137: $requiredPlugin = $this->loadedPlugins[$name];
138:
139: if($dependency->getMinVersion() != Dependency::NO_LIMIT && version_compare($requiredPlugin->getVersion(), $dependency->getMinVersion()) < 0)
140: throw new DependencyTooOldException($plugin, $dependency);
141: if($dependency->getMaxVersion() != Dependency::NO_LIMIT && version_compare($requiredPlugin->getVersion(), $dependency->getMaxVersion()) > 0)
142: throw new DependencyTooNewException($plugin, $dependency);
143: }
144:
145:
146: else if($name == 'ManiaLive')
147: {
148: if($dependency->getMinVersion() != Dependency::NO_LIMIT && version_compare(\ManiaLiveApplication\Version, $dependency->getMinVersion()) < 0)
149: throw new DependencyTooOldException($plugin, $dependency);
150: if($dependency->getMaxVersion() != Dependency::NO_LIMIT && version_compare(\ManiaLiveApplication\Version, $dependency->getMaxVersion()) > 0)
151: throw new DependencyTooNewException($plugin, $dependency);
152: }
153:
154:
155: else
156: throw new DependencyNotFoundException($plugin, $dependency);
157: }
158: }
159:
160: 161: 162: 163:
164: function getPublicMethods($pluginId)
165: {
166: return isset($this->loadedPlugins[$pluginId]) ? $this->loadedPlugins[$pluginId]->getPublicMethods() : null;
167: }
168:
169: 170: 171: 172: 173: 174: 175: 176:
177: function callPublicMethod(Plugin $caller, $pluginId, $pluginMethod, $methodArgs)
178: {
179: if(!isset($this->loadedPlugins[$pluginId]))
180: throw new Exception('Plugin "'.$pluginId.'" which you want to call a method from, does not exist!');
181:
182: $plugin = $this->loadedPlugins[$pluginId];
183: array_push($methodArgs, $caller->getId());
184: $method = $plugin->getPublicMethod($pluginMethod);
185: return $method->invokeArgs($plugin, $methodArgs);
186: }
187:
188: function onInit()
189: {
190: Console::println('[PluginHandler] Start plugin load process:');
191:
192: foreach(\ManiaLive\Application\Config::getInstance()->plugins as $pluginId)
193: {
194: try
195: {
196: $this->register($pluginId);
197: }
198: catch(\Exception $e)
199: {
200: $this->unload($pluginId);
201: ErrorHandling::processRuntimeException($e);
202: }
203: }
204:
205: foreach($this->loadedPlugins as $pluginId => $plugin)
206: {
207: try
208: {
209: $this->prepare($plugin);
210: }
211: catch(\Exception $e)
212: {
213: $this->unload($pluginId);
214: ErrorHandling::processRuntimeException($e);
215: }
216: }
217:
218: foreach($this->loadedPlugins as $plugin)
219: $plugin->onReady();
220:
221: Console::println('[PluginHandler] All registered plugins have been loaded');
222: }
223:
224: function onRun() {}
225: function onPreLoop() {}
226: function onPostLoop() {}
227:
228: function onTerminate()
229: {
230: foreach($this->loadedPlugins as $pluginId => $plugin)
231: $this->unload($pluginId);
232: }
233:
234: function onServerStart()
235: {
236: foreach($this->delayedPlugins as $pluginId => $plugin)
237: {
238: try
239: {
240: $this->loadedPlugins[$pluginId] = $plugin;
241: $this->prepare($plugin);
242: }
243: catch(\Exception $e)
244: {
245: $this->unload($pluginId);
246: unset($this->delayedPlugins[$pluginId]);
247: ErrorHandling::processRuntimeException($e);
248: }
249: }
250:
251: foreach($this->delayedPlugins as $plugin)
252: $plugin->onReady();
253:
254: $this->delayedPlugins = array();
255: }
256:
257: function onServerStop()
258: {
259: foreach($this->loadedPlugins as $pluginId => $plugin)
260: if(!($plugin instanceof WaitingCompliant))
261: {
262: $this->unload($pluginId);
263: $this->delayedPlugins[$pluginId] = $plugin;
264: }
265: }
266:
267: function onPlayerConnect($login, $isSpectator) {}
268: function onPlayerDisconnect($login) {}
269: function onPlayerChat($playerUid, $login, $text, $isRegistredCmd) {}
270: function onPlayerManialinkPageAnswer($playerUid, $login, $answer, array $entries) {}
271: function onEcho($internal, $public) {}
272: function onBeginMatch() {}
273: function onEndMatch($rankings, $winnerTeamOrMap) {}
274: function onBeginMap($map, $warmUp, $matchContinuation) {}
275: function onEndMap($rankings, $map, $wasWarmUp, $matchContinuesOnNextMap, $restartMap) {}
276: function onBeginRound() {}
277: function onEndRound() {}
278: function onStatusChanged($statusCode, $statusName) {}
279: function onPlayerCheckpoint($playerUid, $login, $timeOrScore, $curLap, $checkpointIndex) {}
280: function onPlayerFinish($playerUid, $login, $timeOrScore) {}
281: function onPlayerIncoherence($playerUid, $login) {}
282: function onBillUpdated($billId, $state, $stateName, $transactionId) {}
283: function onTunnelDataReceived($playerUid, $login, $data) {}
284: function onMapListModified($curMapIndex, $nextMapIndex, $isListModified) {}
285: function onPlayerInfoChanged($playerInfo) {}
286: function onManualFlowControlTransition($transition) {}
287: function onVoteUpdated($stateName, $login, $cmdName, $cmdParam) {}
288: function onModeScriptCallback($param1, $param2) {}
289: }
290:
291: class Exception extends \Exception {}
292: