├── GatewayWorker └── Lib │ ├── Utils.php │ ├── Event.php │ ├── MyRedis.php │ ├── EventDispatcher.php │ ├── RedisForDb.php │ ├── ChannelEventDispatcher.php │ └── FrameChild.php └── README.md /GatewayWorker/Lib/Utils.php: -------------------------------------------------------------------------------- 1 | type = $type; 40 | $this->data = $data; 41 | } 42 | 43 | /** 44 | * Prevents any other listeners from receiving the event. 45 | */ 46 | public function stopImmediatePropagation() { 47 | $this->stopsImmediatePropagation = true; 48 | } 49 | 50 | // event pooling 51 | 52 | /** 53 | * 54 | * @param string $type 55 | * @param unknown $data 56 | */ 57 | public static function fromPool($type, $data = null) { 58 | if (count ( Event::$sEventPool )) 59 | return array_pop ( Event::$sEventPool )->reset ( $type, $data ); 60 | else 61 | return new Event ( $type, $data ); 62 | } 63 | 64 | /** 65 | * 66 | * @param Event $event 67 | */ 68 | public static function toPool($event) { 69 | $event->data = $event->target = $event->currentTarget = null; 70 | array_push ( Event::$sEventPool, $event ); 71 | } 72 | 73 | /** 74 | * 75 | * @param string $type 76 | * @param * $data 77 | */ 78 | public function reset($type, $data = null) { 79 | $this->type = $type; 80 | $this->data = $data; 81 | $this->target = $this->currentTarget = null; 82 | $this->stopsImmediatePropagation = false; 83 | return $this; 84 | } 85 | } -------------------------------------------------------------------------------- /GatewayWorker/Lib/MyRedis.php: -------------------------------------------------------------------------------- 1 | pconnect($config['host'], $config['port'])==false){ 39 | die($myRedis->getLastError()); 40 | throw new \Exception("redis not connect"); 41 | } 42 | if($myRedis->auth($config['user'].":".$config['password'])==false){ 43 | die($myRedis->getLastError()); 44 | throw new \Exception("redis not auth"); 45 | } 46 | $myRedis->select($config['select']); 47 | } 48 | return self::$instance[$config_name]; 49 | } 50 | 51 | /** 52 | * 关闭redis实例 53 | * @param string $config_name 54 | */ 55 | public static function close($config_name) 56 | { 57 | if(isset(self::$instance[$config_name])) 58 | { 59 | self::$instance[$config_name]->close(); 60 | self::$instance[$config_name] = null; 61 | } 62 | } 63 | 64 | /** 65 | * 关闭所有redis实例 66 | */ 67 | public static function closeAll() 68 | { 69 | foreach(self::$instance as $connection) 70 | { 71 | $connection->close(); 72 | } 73 | self::$instance = array(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ServerFrame 2 | 基于workerman框架的帧扩展 实现的例子:http://114.55.55.197:9393 3 | 依赖php workerman框架 https://github.com/walkor/Workerman 4 | 配合GatewayWorker框架使用起来更加方便 https://github.com/walkor/gatewayworker 5 | ChannelEventDispatcher 依赖Channel分布式通讯组件 https://github.com/walkor/channel 6 | #如何使用 7 | 使用GatewayWorker框架时将文件复制到GatewayWorker目录下即可自动加载使用,没有使用GatewayWorker框架请参考GatewayWorker框架 8 | 9 | #功能介绍 10 | 1.EventDispatcher 事件派发器,支持中断。用于管理事件的派发。 11 | 2.ChannelEventDispatcher 跨进程事件派发器,原理和EventDispatcher一样,结合Channel组件进行跨进程的事件派发。单例模式 12 | 3.FrameChild 帧处理组件,可嵌套,继承EventDispatcher,支持状态保留reload后恢复数据,自带回收重用,对象池模式。 13 | 4.Event 事件,EventDispatcher将派发Event事件,自带回收重用,对象池模式。 14 | 5.MyRedis Redis 15 | 6.RedisForDb Redis作为Db缓存的基本实现 16 | 7.Utils 工具类,目前仅提供uuid唯一id。 17 | 18 | #EventDispatcher 19 | addEventListener($type, $listener)添加事件侦听 20 | removeEventListener($type, $listener)移除事件侦听 21 | removeEventListeners($type = null)移除type类型或所有的事件侦听 22 | dispatchEvent($event)派发事件 23 | dispatchEventWith($type, $data = null)派发事件 Event使用对象池模式 24 | 注意addEventListener和removeEventListener请成对使用,避免内存溢出 25 | 事例: 26 | ```php 27 | mEventDispatcher->addEventListener('event_changer',array($this,'onChangeListener')); 28 | function onChangeListener(Event $event){ 29 | var_dump($event->type); 30 | var_dump($event->data); 31 | ... 32 | } 33 | ``` 34 | #ChannelEventDispatcher 35 | 用法与EventDispatcher一致,唯一注意的就是ChannelEventDispatcher是单例模式 36 | ```php 37 | ChannelEventDispatcher::$channelAddress='127.0.0.0:8081';//设置channel地址 38 | ChannelEventDispatcher::getChannelEventDispatcher()->addEventListener('event_changer',array($this,'onChangeListener')); 39 | ``` 40 | 进阶应用,获取Event后可以通过EventDispatcher继续派发下去,这样就完成了跨进程间和进程中的完整消息派发。 41 | #FrameChild 42 | 以下on方法继承时请override 43 | onEnterFrame();每一帧都会触发 44 | onAdded();每次被add都会触发,reload时不会触发 45 | onResume();每次被add和reload时都会触发,用于恢复状态 46 | onRemoved();每次被remove时都会触发,remove不会解除自身包含child的状态 47 | onDestory();销毁时会调用,并销毁所有的child 48 | addChild($frameChild, $key, $isReload = false) 往自身添加child,reload状态系统会调用,开发者不要赋值 49 | removeChild($key)移除子控件,触发子控件的onRemoved方法 50 | removeChilden()移除所有的子控件,触发所有child的onRemoved方法 51 | removeFromParent()从父级移除自己,触发自身onRemoved方法 52 | getChild($key)获取child 53 | destory()销毁触发所有child的onRemoved,onDestory方法 54 | getChildrenNum()获取child数量 55 | pushToMessage()插入消息列表,内部维护一个消息列表,reload不会丢失 56 | shiftFromMessage()取出消息 57 | shiftAllFromMessage()取出所有消息 58 | parent获取父FrameChild对象 59 | 60 | 以下是FrameChild使用时的注意事项 61 | 1.继承FrameChild时构造函数不允许传参数 62 | 2.对象从对象池中获取,请使用FrameChild::getFromPool($class),$class填写包含命名空间的完整类名 63 | 3.尽量使用FrameChild::getFromPool方式创建FrameChild对象,他会在destory后自动回收,循环利用 64 | 4.由于workerman有reload机制,正常状态下重启worker加载新的代码,新的进程会使你丢失所有运行时的数据,使用FrameChild的约束,会自动帮你存储数据,恢复数据,重建所有的FrameChild树。 65 | 使用FrameChild的__set()和__get()魔术方法,当你需要传进一个变量时$frameChild->count = 10; 66 | 当你需要取得一个变量时var_dump($frameChild->count) 67 | 如果你这么做count的数据再下次reload时会被保留下来 68 | 这里有个小小的体验改善,child的对象__get()不到数据时会自动向parent请求,所以: 69 | ```php 70 | $frameParent->addChild($frameChild); 71 | $frameParent->count=10; 72 | echo $frameChild->count; 73 | ``` 74 | 会输出10。 75 | 5.如果要在workerman中使用FrameChild按照以下步骤 76 | onWorkerStart()方法中添加以下代码 77 | ```php 78 | /** 79 | * 参与帧循环的root原件 80 | * 81 | * @var FrameChild 82 | */ 83 | /** 84 | * 帧频 85 | * 86 | * @var int 87 | */ 88 | public $frameRate = 0; 89 | public $frameRoot = null; 90 | protected function onWorkerStart() { 91 | $this->frameRoot = new FrameChild (); 92 | $this->frameRoot->__onAdded ( $this, false ); 93 | // 如果使能了EnterFrame就启动Time 94 | if ($this->frameRate > 0) { 95 | Timer::add(0.5, array($this,'startFrameTimer'),array(),false); 96 | if (isset ( $this->saveData ['@frameAutoSaveData'] )) { 97 | $this->frameRoot->loadData ( $this->saveData ['@frameAutoSaveData'] ); 98 | } 99 | } 100 | } 101 | /** 102 | * 启动frame定时器 103 | */ 104 | public function startFrameTimer(){ 105 | Timer::add ( 1 / $this->frameRate, array ( 106 | $this->frameRoot, 107 | '__onEnterFrame' 108 | ) ); 109 | } 110 | ``` 111 | 112 | 113 | -------------------------------------------------------------------------------- /GatewayWorker/Lib/EventDispatcher.php: -------------------------------------------------------------------------------- 1 | _eventListeners )) { 22 | $this->_eventListeners [$type] = array (); 23 | } 24 | if (! in_array ( $listener, $this->_eventListeners [$type] )) { 25 | array_push ( $this->_eventListeners [$type], $listener ); 26 | } 27 | } 28 | 29 | /** 30 | * Removes an event listener from the object. 31 | * 32 | * @param string $type 33 | * @param function $listener 34 | */ 35 | public function removeEventListener($type, $listener) { 36 | if (array_key_exists ( $type, $this->_eventListeners )) { 37 | $numListeners = count ( $this->_eventListeners [$type] ); 38 | } else { 39 | $numListeners = 0; 40 | } 41 | if ($numListeners > 0) { 42 | $index = array_search ( $listener, $this->_eventListeners [$type] ); 43 | if ($index!==null) { 44 | unset ( $this->_eventListeners [$type] [$index] ); 45 | } 46 | } 47 | if ($numListeners == 0) { 48 | unset ( $this->_eventListeners [$type] ); 49 | } 50 | } 51 | 52 | /** 53 | * Removes all event listeners with a certain type, or all of them if type is null. 54 | * Be careful when removing all event listeners: you never know who else was listening. 55 | * 56 | * @param string $type 57 | */ 58 | public function removeEventListeners($type = null) { 59 | if ($type) { 60 | unset ( $this->_eventListeners [$type] ); 61 | } else { 62 | $this->_eventListeners = array (); 63 | } 64 | } 65 | 66 | /** 67 | * Dispatches an event to all objects that have registered listeners for its type. 68 | * If an event with enabled 'bubble' property is dispatched to a display object, it will 69 | * travel up along the line of parents, until it either hits the root object or someone 70 | * stops its propagation manually. 71 | * 72 | * @param Event $event 73 | */ 74 | public function dispatchEvent($event) { 75 | if (! array_key_exists ( $event->type, $this->_eventListeners )) { 76 | return; // no need to do anything 77 | } 78 | // we save the current target and restore it later; 79 | // this allows users to re-dispatch events without creating a clone. 80 | 81 | $previousTarget = $event->target; 82 | $event->target = $this; 83 | $this->invokeEvent ( $event ); 84 | if ($previousTarget) 85 | $event->target = $previousTarget; 86 | } 87 | 88 | /** 89 | * @private 90 | * Invokes an event on the current object. 91 | * This method does not do any bubbling, nor 92 | * does it back-up and restore the previous target on the event. The 'dispatchEvent' 93 | * method uses this method internally. 94 | * 95 | * @param Event $event 96 | */ 97 | private function invokeEvent($event) { 98 | if (array_key_exists ( $event->type, $this->_eventListeners )) { 99 | $listeners = $this->_eventListeners [$event->type]; 100 | $numListeners = count ( $listeners ); 101 | } 102 | if ($numListeners) { 103 | $event->currentTarget = $this; 104 | foreach ( $listeners as $listener ) { 105 | call_user_func ( $listener, $event ); 106 | if ($event->stopsImmediatePropagation) { 107 | return true; 108 | } 109 | } 110 | return $event->stopsImmediatePropagation; 111 | } else { 112 | return false; 113 | } 114 | } 115 | 116 | /** 117 | * Dispatches an event with the given parameters to all objects that have registered 118 | * listeners for the given type. 119 | * The method uses an internal pool of event objects to 120 | * avoid allocations. 121 | * 122 | * @param string $type 123 | */ 124 | public function dispatchEventWith($type, $data = null) { 125 | $event = Event::fromPool ( $type, $data ); 126 | $this->dispatchEvent ( $event ); 127 | Event::toPool ( $event ); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /GatewayWorker/Lib/RedisForDb.php: -------------------------------------------------------------------------------- 1 | hMset($key,$set_array); 31 | } 32 | /** 33 | * 从redis中批量获取,方法不保证全部都能有值 34 | * @param string $redis_name 35 | * @param string $key 36 | * @param array $field 为空代表取全部 例子array(1,2,3) 37 | * @return array 额定为二维数组,不存在返回null 38 | */ 39 | public static function getHashFromRedis($redis_name,$key,$fields=null){ 40 | $redis = MyRedis::instance($redis_name); 41 | if(empty($fields)){ 42 | $result = $redis->hGetAll($key); 43 | }else{ 44 | $result = $redis->hmGet($key, $fields); 45 | } 46 | if($result){//存在 47 | foreach ($result as $key=>$value){ 48 | $result[$key]=json_decode($value,true); 49 | } 50 | return $result; 51 | }else { 52 | return null; 53 | } 54 | } 55 | /** 56 | * 先从redis中找,找不到再从db中找,存入redis 57 | * @param DbConnection $db_r 数据库查询方法,不要存在where in语句如果在redis中没有命中where方法将被修改 58 | * @param string $redis_name 59 | * @param string $key hash键名 60 | * @param string $field_name_db field的唯一名称,数据存入hash的唯一索引,field必须是数据库存在的字段名 61 | * @param array $fields $field_name_db在数据库对应的值的数组 62 | * @return array 找不到为null,否则额定为二维数组@data_from后面表示数据来源 63 | */ 64 | public static function getHashFromRedisAndDb($redis_name,$key,$field_name_db,$fields,DbConnection $db_r){ 65 | $result = RedisForDb::getHashFromRedis($redis_name,$key,$fields); 66 | $needSeachFromDb = array(); 67 | foreach ($fields as $value){ 68 | if(empty($result[$value])){ 69 | array_push($needSeachFromDb, $value); 70 | }else{ 71 | $result[$value]['@data_from']='redis'; 72 | } 73 | } 74 | if(!empty($needSeachFromDb)){//此处处理未命中数据,注意where方法将被修改 75 | if(count($needSeachFromDb)>1){ 76 | $resultFromDb = $db_r->where($field_name_db.' in ('.implode(',',$needSeachFromDb).')')->query();//向db请求 77 | }else{ 78 | $resultFromDb = $db_r->where($field_name_db."='".$needSeachFromDb[0]."'")->query();//向db请求 79 | } 80 | if(empty($resultFromDb)){//代表从数据库中找不到 81 | return null; 82 | } 83 | RedisForDb::putToRedisHash($redis_name,$key,$resultFromDb,$field_name_db); //写入redis 84 | foreach ($resultFromDb as $value){ 85 | $result[$value[$field_name_db]]=$value; //拼合数据 86 | $result[$value[$field_name_db]]['@data_from']='db'; 87 | } 88 | } 89 | return $result; 90 | } 91 | /** 92 | * 批量更新Redis中的数据(不支持插入数据),先从redis批量获取数据->合并数据->传回redis,该方法适合不完整的数据合并, 93 | * 插入数据请直接使用putToRedisHash,切记 94 | * 如果redis中不含有对应的数据,强制合并后的数据也将是不完整的, 95 | * 所以redis中不存在的数据,使用该方法将会忽略掉。 96 | * @param string $redis_name 97 | * @param string $key 98 | * @param array $updateValueArrary 二维数组符合redisfordb返回的结构{$field=>{},$field=>{}}; 99 | */ 100 | public static function updateHashToRedis($redis_name,$key,$updateValueArrary){ 101 | $fields = array_keys($updateValueArrary); 102 | $resultFromRedis = RedisForDb::getHashFromRedis($redis_name,$key,$fields); 103 | foreach ($resultFromRedis as $r_key=>$r_value){//剔除不完整的数据 104 | if(empty($r_value)){ 105 | unset($fields[$r_key]); 106 | } 107 | } 108 | $result = array(); 109 | foreach ($fields as $value){ 110 | $result[$value] = json_encode(array_merge($resultFromRedis[$value],$updateValueArrary[$value])); 111 | } 112 | $redis = MyRedis::instance($redis_name); 113 | $redis->hMset($key,$result); 114 | } 115 | } 116 | 117 | ?> -------------------------------------------------------------------------------- /GatewayWorker/Lib/ChannelEventDispatcher.php: -------------------------------------------------------------------------------- 1 | log ( "ERROR:not set ChannelAddress" ); 26 | } 27 | } 28 | return ChannelEventDispatcher::$channelEventDispatcher; 29 | } 30 | public function addEventListener($type, $listener) { 31 | if (! array_key_exists ( $type, $this->_eventListeners )) { 32 | $this->_eventListeners [$type] = array (); 33 | Client::on ( $type, array ( 34 | $this, 35 | 'onChannelCallBack' 36 | ) ); 37 | } 38 | if (! in_array ( $listener, $this->_eventListeners [$type] )) { 39 | array_push ( $this->_eventListeners [$type], $listener ); 40 | } 41 | } 42 | /** 43 | * channel的回复 44 | * 45 | * @param unknown $data 46 | */ 47 | public function onChannelCallBack($data) { 48 | if (! array_key_exists ( $data ['type'], $this->_eventListeners )) { 49 | return; // no need to do anything 50 | } 51 | if (isset ( $data ['data'] )) { 52 | $tempData = unserialize ( $data ['data'] ); 53 | } else { 54 | $tempData = null; 55 | } 56 | $event = Event::fromPool ( $data ['type'], $tempData ); 57 | $previousTarget = $event->target; 58 | $event->target = $this; 59 | $this->invokeEvent ( $event ); 60 | if ($previousTarget) 61 | $event->target = $previousTarget; 62 | Event::toPool ( $event ); 63 | } 64 | /** 65 | * Removes an event listener from the object. 66 | * 67 | * @param string $type 68 | * @param function $listener 69 | * @param bool $ifAllWorker 70 | */ 71 | public function removeEventListener($type, $listener) { 72 | if (array_key_exists ( $type, $this->_eventListeners )) { 73 | $numListeners = count ( $this->_eventListeners [$type] ); 74 | } else { 75 | $numListeners = 0; 76 | } 77 | if ($numListeners > 0) { 78 | $index = array_search ( $listener, $this->_eventListeners [$type] ); 79 | if ($index !== null) { 80 | unset ( $this->_eventListeners [$type] [$index] ); 81 | } 82 | } 83 | if ($numListeners == 0) { 84 | unset ( $this->_eventListeners [$type] ); 85 | Client::unsubscribe ( $type ); 86 | } 87 | } 88 | 89 | /** 90 | * Removes all event listeners with a certain type, or all of them if type is null. 91 | * Be careful when removing all event listeners: you never know who else was listening. 92 | * 93 | * @param string $type 94 | */ 95 | public function removeEventListeners($type = null) { 96 | if ($type) { 97 | unset ( $this->_eventListeners [$type] ); 98 | Client::unsubscribe ( $type ); 99 | } else { 100 | foreach ( $this->_eventListeners as $key => $value ) { 101 | Client::unsubscribe ( $key ); 102 | } 103 | $this->_eventListeners = array (); 104 | } 105 | } 106 | 107 | /** 108 | * Dispatches an event to all objects that have registered listeners for its type. 109 | * 110 | * @param Event $event 111 | */ 112 | public function dispatchEvent($event) { 113 | $data ['type'] = $event->type; 114 | if ($event->data != null) { 115 | $data ['data'] = serialize ( $event->data ); 116 | } 117 | Client::publish ( $data ['type'], $data ); 118 | } 119 | 120 | /** 121 | * @private 122 | * Invokes an event on the current object. 123 | * This method does not do any bubbling, nor 124 | * does it back-up and restore the previous target on the event. The 'dispatchEvent' 125 | * method uses this method internally. 126 | * 127 | * @param Event $event 128 | */ 129 | private function invokeEvent($event) { 130 | if (array_key_exists ( $event->type, $this->_eventListeners )) { 131 | $listeners = $this->_eventListeners [$event->type]; 132 | $numListeners = count ( $listeners ); 133 | } 134 | if ($numListeners) { 135 | $event->currentTarget = $this; 136 | foreach ( $listeners as $listener ) { 137 | call_user_func ( $listener, $event ); 138 | if ($event->stopsImmediatePropagation) { 139 | return true; 140 | } 141 | } 142 | return $event->stopsImmediatePropagation; 143 | } else { 144 | return false; 145 | } 146 | } 147 | 148 | /** 149 | * Dispatches an event with the given parameters to all objects that have registered 150 | * listeners for the given type. 151 | * The method uses an internal pool of event objects to 152 | * avoid allocations. 153 | * 154 | * @param string $type 155 | */ 156 | public function dispatchEventWith($type, $data = null) { 157 | $event = Event::fromPool ( $type, $data ); 158 | $this->dispatchEvent ( $event ); 159 | Event::toPool ( $event ); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /GatewayWorker/Lib/FrameChild.php: -------------------------------------------------------------------------------- 1 | 0) { 29 | $temp = array_shift ( FrameChild::$pool [$class] ); 30 | } 31 | } 32 | if ($temp == null) { 33 | $class = new \ReflectionClass ( $class ); 34 | $temp = $class->newInstanceArgs ( [ ] ); 35 | } 36 | return $temp; 37 | } 38 | /** 39 | * 放入池子 40 | * 41 | * @param FrameChild $intance 42 | */ 43 | public static function putToPool(FrameChild $intance, $class) { 44 | if (! array_key_exists ( $class, FrameChild::$pool )) { 45 | FrameChild::$pool [$class] = array (); 46 | } 47 | array_push ( FrameChild::$pool [$class], $intance ); 48 | } 49 | /** 50 | * $thriftBusinessWorker 51 | * 52 | * @var ThriftBusinessWorker 53 | */ 54 | protected $thriftBusinessWorker; 55 | /** 56 | * 防止重复用的计数器 57 | * 58 | * @var int 59 | */ 60 | protected $count = 0; 61 | /** 62 | * 自身的saveData 63 | */ 64 | protected $saveData = array (); 65 | /** 66 | * 父容器 67 | * 68 | * @var FrameChild 69 | */ 70 | public $parent; 71 | /** 72 | * 参与帧循环的子原件 73 | * 74 | * @var array $children_list 75 | */ 76 | public $children_list = array (); 77 | /** 78 | * 在父级的位置 79 | * 80 | * @var string 81 | */ 82 | public $key = null; 83 | /** 84 | * 存活时间-1代表不限,最好都有最长存活时间(秒) 85 | * 86 | * @var int 87 | */ 88 | public $maxLifeTime = - 1; 89 | /** 90 | * 准备销毁 91 | * 92 | * @var string $readyDestory 93 | */ 94 | public $readyDestory = false; 95 | /** 96 | * 准备移除 97 | * 98 | * @var string $readyDestory 99 | */ 100 | public $readyRemove = false; 101 | /** 102 | * 已存活时间 103 | * 104 | * @var integer 105 | */ 106 | protected $startTime = 0; 107 | /** 108 | * 当前帧 109 | * 110 | * @var integer $currentFrame 111 | */ 112 | protected $currentFrame = 0; 113 | protected $messageList = array (); 114 | /** 115 | * 帧循环 116 | */ 117 | public function __onEnterFrame() { 118 | foreach ( $this->children_list as $key => $value ) { 119 | $need = $value->__onEnterFrame (); 120 | if (! $need) { 121 | unset ( $this->children_list [$key] ); 122 | } 123 | } 124 | if ($this->readyDestory) { // 准备销毁了 125 | $this->onRemoved (); 126 | $this->onDestory (); 127 | $this->__reset (); 128 | return false; 129 | } elseif ($this->readyRemove) { // 准备移除了 130 | $this->__onRemoved (); 131 | return false; 132 | } else { 133 | $this->onEnterFrame (); 134 | $this->currentFrame ++; 135 | if ($this->maxLifeTime != - 1 && time () - $this->startTime >= $this->maxLifeTime) { // 到达存活上限 136 | $this->readyDestory = true; 137 | } 138 | } 139 | return true; 140 | } 141 | public function onEnterFrame() { 142 | } 143 | /** 144 | * 加入循环 145 | */ 146 | public function __onAdded(ThriftBusinessWorker $thriftBusinessWorker, $isReload) { 147 | $this->thriftBusinessWorker = $thriftBusinessWorker; 148 | $this->startTime = time (); 149 | $this->onResume (); 150 | if (! $isReload) { 151 | $this->onAdded (); 152 | } 153 | } 154 | /** 155 | * reload和create都执行 156 | */ 157 | public function onResume() { 158 | } 159 | /** 160 | * 只执行一次 161 | */ 162 | public function onAdded() { 163 | } 164 | /** 165 | * 移除循环时 166 | */ 167 | private function __onRemoved() { 168 | $this->onRemoved (); 169 | $this->parent = null; 170 | $this->key = null; 171 | } 172 | public function onRemoved() { 173 | } 174 | /** 175 | * 加入子控件 176 | * 177 | * @param FrameChild $frameChild 178 | * @param string $key 179 | * @param bool $isReload 180 | * @return string $key key重复将后面接@尾数 181 | */ 182 | public function addChild($frameChild, $key, $isReload = false) { 183 | $frameChild->removeFromParent (); 184 | $frameChild->parent = $this; 185 | $frameChild->__onAdded ( $this->thriftBusinessWorker, $isReload ); 186 | if ($isReload) { 187 | // 覆盖 188 | $this->children_list [$key] = $frameChild; 189 | $frameChild->key = $key; 190 | return $frameChild->key; 191 | } else { 192 | if (array_key_exists ( $key, $this->children_list )) { // key重复 193 | $key = $key . '@' . $this->count; 194 | $this->count ++; 195 | } 196 | $this->children_list [$key] = $frameChild; 197 | $frameChild->key = $key; 198 | return $frameChild->key; 199 | } 200 | } 201 | /** 202 | * 移除子控件 203 | * 204 | * @param unknown $index 205 | * @return boolean 206 | */ 207 | public function removeChild($key) { 208 | if (array_key_exists ( $key, $this->children_list )) { 209 | $this->children_list [$key]->readyRemove = true; 210 | return true; 211 | } 212 | return false; 213 | } 214 | /** 215 | * 移除所有的子控件 216 | */ 217 | public function removeChilden() { 218 | foreach ( $this->children_list as $value ) { 219 | $value->readyRemove = true; 220 | } 221 | } 222 | /** 223 | * 从父级移除自己 224 | */ 225 | public function removeFromParent() { 226 | if (isset ( $this->parent )) { 227 | $this->parent->removeChild ( $this->key ); 228 | } 229 | } 230 | /** 231 | * 获取child 232 | * 233 | * @param string $key 234 | * @return FrameChild|NULL 235 | */ 236 | public function getChild($key) { 237 | if (array_key_exists ( $key, $this->children_list )) { 238 | return $this->children_list [$key]; 239 | } else { 240 | return null; 241 | } 242 | } 243 | /** 244 | * 重置,回收 245 | */ 246 | private function __reset() { 247 | $this->thriftBusinessWorker = null; 248 | $this->count = 0; 249 | $this->parent = null; 250 | $this->children_list = array (); 251 | $this->saveData = array (); 252 | $this->currentFrame = 0; 253 | $this->key = null; 254 | $this->maxLifeTime = - 1; 255 | $this->readyDestory = false; 256 | $this->messageList = array (); 257 | $this->startTime = 0; 258 | $this->readyRemove = false; 259 | $key = null; 260 | FrameChild::putToPool ( $this, get_called_class () ); 261 | } 262 | /** 263 | * 销毁 264 | */ 265 | public function destory() { 266 | foreach ( $this->children_list as $value ) { 267 | $value->destory (); 268 | } 269 | $this->readyDestory = true; 270 | $this->removeFromParent (); 271 | $this->removeEventListeners (); 272 | } 273 | public function onDestory() { 274 | } 275 | /** 276 | * 保存数据 277 | * 278 | * @param unknown $saveData 279 | */ 280 | public function saveData(&$saveData) { 281 | $this->saveData ['@class_name'] = get_called_class (); 282 | $this->saveData ['@count'] = $this->count; 283 | $this->saveData ['@key'] = $this->key; 284 | $this->saveData ['@children_list'] = array (); 285 | $this->saveData ['@startTime'] = $this->startTime; 286 | $this->saveData ['@maxLifeTime'] = $this->maxLifeTime; 287 | $this->saveData ['@readyDestory'] = $this->readyDestory; 288 | $this->saveData ['@readyRemove'] = $this->readyRemove; 289 | $this->saveData ['@messageList'] = $this->messageList; 290 | $saveData = $this->saveData; 291 | foreach ( $this->children_list as $key => $value ) { 292 | $saveData ['@children_list'] [$key] = array (); 293 | $value->saveData ( $saveData ['@children_list'] [$key] ); 294 | } 295 | } 296 | /** 297 | * 读取数据 298 | * 299 | * @param unknown $saveData 300 | */ 301 | public function loadData($saveData) { 302 | $this->saveData = $saveData; 303 | $this->count = $this->saveData ['@count']; 304 | $this->key = $this->saveData ['@key']; 305 | $this->startTime = $this->saveData ['@startTime']; 306 | $this->maxLifeTime = $this->saveData ['@maxLifeTime']; 307 | $this->readyDestory = $this->saveData ['@readyDestory']; 308 | $this->readyRemove = $this->saveData ['@readyRemove']; 309 | $this->messageList = $this->saveData ['@messageList']; 310 | foreach ( $this->saveData ['@children_list'] as $key => $value ) { 311 | $class = new \ReflectionClass ( $value ['@class_name'] ); 312 | $instance = $class->newInstanceArgs ( [ ] ); 313 | $this->addChild ( $instance, $key, true ); 314 | $instance->loadData ( $saveData ['@children_list'] [$key] ); 315 | } 316 | } 317 | public function __set($name, $value) { 318 | $this->saveData [$name] = $value; 319 | } 320 | /** 321 | * 找不到会从父集中找 322 | * 323 | * @param unknown $name 324 | */ 325 | public function &__get($name) { 326 | if (array_key_exists ( $name, $this->saveData )) { 327 | return $this->saveData [$name]; 328 | } elseif ($this->parent !== null) { 329 | return $this->parent->__get ( $name ); 330 | } else { 331 | throw new \Error ( "$name not find in " . get_called_class () ); 332 | } 333 | } 334 | /** 335 | * 获取child数量 336 | */ 337 | public function getChildrenNum() { 338 | return count ( $this->children_list ); 339 | } 340 | /** 341 | * 插入消息列表 342 | * 343 | * @param unknown $value 344 | */ 345 | public function pushToMessage($value) { 346 | array_push ( $this->messageList, $value ); 347 | } 348 | /** 349 | * 取出消息 350 | * 351 | * @return mixed 352 | */ 353 | public function shiftFromMessage() { 354 | return array_shift ( $this->messageList ); 355 | } 356 | /** 357 | * 取出所有消息 358 | * 359 | * @return mixed 360 | */ 361 | public function shiftAllFromMessage() { 362 | return array_splice ( $this->messageList, 0 ); 363 | } 364 | } 365 | --------------------------------------------------------------------------------