среда, 8 февраля 2017 г.

Как не надо кодить. Создание и ликвидация бага своими руками.

Я не раз замечал за собой особенность создавать такие ситуации, которые, по-видимому, кроме меня никто не создаст. Во всяком случае в здравом уме, трезвом состоянии и твердой памяти. По-видимому, это у меня такой талант.
Продолжая работу над проектом, я внезапно столкнулся с одной очень специфической проблемой. Как известно, если самолет ударяется об землю, он взрывается. Дальше все зависти от фантазии автора проекта - появляется ли черный дымщийся остов или самолет просто исчезает. В любом случае юнит "убирается с доски". Так вот, неожиданно выяснилось, что если об землю разбивается самолет игрока, то после нажатия кнопки Esc немедленно следует вылет Блендера. Причем было непонятно, что это за баг. dron написал файл для запуска файла через блендерплейер с выводом ошибок в файл error, но ситуацию это не прояснило. Файл оставался пустым.
Баг крайне непрятный и его надо было ликвидировать любой ценой. Но и понять, в чем причина было сложно. я рассуждал так: если вылет следует после исчезновения самолета игрока, значит, это как-то связано с управлением. спешно переделал код управления клавишами - уже тогда у меня появились подозрения, что в этом виновато исезновение оверлейной сцены кабины. Запустил - то же самое. Хорошо, тогда почистим скрипт управления камерами. Дело в том, что я по жадности своей и лени не стал, как в первой версии делать обмен данными между сценами через глобальный словарь, а запускал цикл проверки списка сцен с выбором сцены кабины и дальше уже традиционно обращался к scene.objects с названием объекта. Как говорится: "Жадность фраера сгубила." Однако и после зачистки скрипта камер веселье продолжилось. У меня оставался один-единственный скрипт, где еще существовал вызов списка сцен. А теперь смотрим...

def controlUnit():
    cont = bge.logic.getCurrentController()
    own = cont.owner
    scene = bge.logic.getCurrentScene()
    sensor = cont.sensors["unit"]
    kontakt = cont.sensors["kontakt"]
   
    #Название пропенрти для механики ведомого - при условии
    #что юнит - ведущий, при выпуске тормоза, крыла, шасси и тп он передает команду на ведомого для выполнения такой же операции
    mechanicWingman = ''
   
    #словарь свойств - для анимации, применения оружия и смены подвесок, плюс отслеживание повреждений
    if 'localDict' not in own:
        controlUnitStart()
       
    #Активная камера в сцене
    activeCam = scene.active_camera
       
    #Камера в кокпите юнита игрока
    cameraPilot = scene.objects["CameraPilot"]
   
    #Камера внешнего обзора, она же камера телевизионного прицела
    cameraWorld = scene.objects["CameraWorld"]
   
    #Центр юнита - опорный объект для всх потомков, он же нужен для смены меша на меш номера, объект присутствует изначально, это потомок
    #двигателя, причем неубираемый, также он нужен для анимации проворота фюзеляжа при выпуске-уборке шасси, поскольку на земле многие
    #машины не стоят ровно - как правилог их нос приподнят - больше или меньше
    CntAircraft = own.childrenRecursive['CntAircraft']
   
    #Это - собственно, полет
    if sensor.positive:
       
        #Контакт с ландшафтом
        if kontakt.positive: 
            #столкновение с землей сбитого самолета или вертолета
            if own['crash'] < 0.2 or own['CHASSY'] < 98:
                if 'CameraPilot' in own.childrenRecursive:
                    own.childrenRecursive['CameraPilot'].removeParent()
                    #Убираем оверлейную сцену
                    sceneList = bge.logic.getSceneList()
                    for sceneGame in sceneList:
                        if sceneGame.name == 'Cockpit':
                            sceneGame.end()
               
                CrashPropertyAir()
           
                import ControlScene
                ControlScene.nearUnitExplode()
                #Убираем сам объект
                own.endObject()
       
       
        if own['bot'] == 0:
            #Вызов управления юнита игрока с помощью клавиатуры, вызов идет постоянно
           
            bge.logic.globalDict['idTarget'] = own['idTarget']
            import ClassGamer
            ClassGamer.control()
        elif own['bot'] > 0:
            #Вызов искусственного интеллекта - необходимо доработать, поскольку own['bot']=1 - взлет, 2 - посадка, 3 - полет по маршруту,
            import ClassBotAir
            ClassBotAir.control()
       
        #Пока ЛА исправен, он может лететь
        if own['crash'] > 0 and own['bot'] > -1:
            import ClassEngineAir
            ClassEngineAir.control()
       
        #Падение по балиистической кривой для сбитого
        elif own['crash'] < 0.1 and own['bot'] > -1:
            unit_Crash = __import__(own['unitModule'])
            unit_Crash.CRASHscenery()
            #Для самолета игрока убираем сцену оверлей и камеру пилота
            if own['bot'] == 0:
                if 'sceneryCrash' in own:
                    if own['sceneryCrash']['timerCrash'] == 1: 
                        if 'CameraPilot' in own.childrenRecursive:
                            own.childrenRecursive['CameraPilot'].removeParent()
                    #Убираем оверлейную сцену
                    sceneList = bge.logic.getSceneList()
                    for sceneGame in sceneList:
                        if sceneGame.name == 'Cockpit':
                            sceneGame.end()        
            import ClassEngineAir
            ClassEngineAir.traectoryCrash()
         
        #Просчет уровней детализации
        #Высокий - детализированная анимированная модель
       
        if own.getDistanceTo(activeCam) < 200:
            if activeCam.getVectTo(own)[2][2] < -0.8 and abs(activeCam.getVectTo(own)[2][1]) < 0.4 and abs(activeCam.getVectTo(own)[2][0]) < 0.24:
                own['levelsDetails'] = 0
        #Средний - детализированная неанимированная модель
        elif 200 < own.getDistanceTo(activeCam) < 3500:
            if activeCam.getVectTo(own)[2][2] < -0.8 and abs(activeCam.getVectTo(own)[2][1]) < 0.4 and abs(activeCam.getVectTo(own)[2][0]) < 0.24:
                own['levelsDetails'] = 1
        #Низкий - малодетализированная модель
        elif 3500 < own.getDistanceTo(activeCam) < 10000:
            if activeCam.getVectTo(own)[2][2] < -0.8 and abs(activeCam.getVectTo(own)[2][1]) < 0.4 and abs(activeCam.getVectTo(own)[2][0]) < 0.24:
                own['levelsDetails'] = 2
        #Малый - модели, как таковой, нет
        elif own.getDistanceTo(activeCam) > 10000:
            if activeCam.getVectTo(own)[2][2] < -0.8 and abs(activeCam.getVectTo(own)[2][1]) < 0.4 and abs(activeCam.getVectTo(own)[2][0]) < 0.24:
                own['levelsDetails'] = 3
       
       
           
        unit_module = __import__(own['unitModule'])
        unit_module.correctData()
       
        ####################################
        #Смена уровня детализации
        if own['localDict']['levelsDetails'] != own['levelsDetails']:
            ClassOperation.LOD()
            own['localDict']['levelsDetails'] = own['levelsDetails']
           
        if own['localDict']['dvig'] != own['dvig']:
            ClassOperation.dvig()
            own['localDict']['dvig'] = own['dvig']
           
        if own['localDict']['crash'] != own['crash']:
            import ClassCrash
            ClassCrash.CrashModel()
            own['localDict']['crash'] = own['crash']
       
        #Пробитие звукового барьера  
        if own['localDict']['sonicBoom'] != own['sonicBoom']:
            ClassOperation.sonicBoom()
            own['localDict']['sonicBoom'] = own['sonicBoom']
       
        if own['localDict']['startLO'] != own['startLO']:
            for generatorLO in own.childrenRecursive:
                if generatorLO.name == 'generatorLO':
                    generatorLO['startLO'] = own['startLO']
            own['localDict']['startLO'] = own['startLO']
       
        if own['levelsDetails'] == 0:          
            #Работа проперти рысканья              
            if own['rotatZ'] != 0 or own['localDict']['YAW'] != own['YAW']:
                unit_module.yaw()
            #Работа проперти тангажа              
            if own['rotatY'] != 0 or own['localDict']['ROLL'] != own['ROLL']:
                unit_module.roll()
            #Работа проперти тангажа              
            if own['rotatX'] != 0 or own['localDict']['PITCH'] != own['PITCH']:
                unit_module.pitch()
           
        #Стандартная операция - отсчет кадров с помощью проперти
        #Шасси
        if own['localDict']['CHASSY'] != own['CHASSY']:
            unit_module.chassy()
            #Стандартная опреация для проигрывания звука
            if own['localDict']['CHASSY'] > own['CHASSY']:
                if own['localDict']['CHASSY'] == own['CHASSY'] + 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'GEAR'
                    audioUnit()
            if own['localDict']['CHASSY'] < own['CHASSY']:
                if own['localDict']['CHASSY'] == own['CHASSY'] - 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'GEAR'
                    audioUnit()
       
        #Тормоз
        if own['localDict']['AIRBRAKE'] != own['AIRBRAKE']:
            unit_module.airbrake()
            #Стандартная опреация для проигрывания звука
            if own['localDict']['AIRBRAKE'] > own['AIRBRAKE']:
                if own['localDict']['AIRBRAKE'] == own['AIRBRAKE'] + 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'AIRBRAKE'
                    audioUnit()
            if own['localDict']['AIRBRAKE'] < own['AIRBRAKE']:
                if own['localDict']['AIRBRAKE'] == own['AIRBRAKE'] - 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'AIRBRAKE'
                    audioUnit()
            #Передача команды ведомому срабатывает только один раз
            #Заодно и передается название проперти, которое надо подтянуть
            if own['statusDict']['listWingman'][0] != '1':
                if abs(own['localDict']['AIRBRAKE'] - own['AIRBRAKE']) == 2:
                    mechanicWingman = 'AIRBRAKE'
                    controlUnitWingman(own, mechanicWingman)
                   
           
        #Перекладка крыла - для самолетоа с КИС
        if own['localDict']['WINGS'] != own['WINGS']:
            unit_module.wings()
            #Стандартная опреация для проигрывания звука
            if own['localDict']['WINGS'] > own['WINGS']:
                if own['localDict']['WINGS'] == own['WINGS'] + 2:
                    own['localDict']['audioLife'] = 230
                    own['localDict']['audioProp'] = 'FLAPS'
                    audioUnit()
            if own['localDict']['WINGS'] < own['WINGS']:
                if own['localDict']['WINGS'] == own['WINGS'] - 2:
                    own['localDict']['audioLife'] = 230
                    own['localDict']['audioProp'] = 'FLAPS'
                    audioUnit()
            #Передача команды ведомому срабатывает только один раз
            #Заодно и передается название проперти, которое надо подтянуть
            if own['statusDict']['listWingman'][0] != '1':
                if abs(own['localDict']['WINGS'] - own['WINGS']) == 2:
                    mechanicWingman = 'WINGS'
                    controlUnitWingman(own, mechanicWingman)
             
        #Закрылки
        if own['localDict']['FLAPS'] != own['FLAPS']:
            unit_module.flaps()
            #Стандартная опреация для проигрывания звука
            if own['localDict']['FLAPS'] > own['FLAPS']:
                if own['localDict']['FLAPS'] == own['FLAPS'] + 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'FLAPS'
                    audioUnit()
            if own['localDict']['FLAPS'] < own['FLAPS']:
                if own['localDict']['FLAPS'] == own['FLAPS'] - 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'FLAPS'
                    audioUnit()
           
           
        #Предкрылки
        if own['localDict']['SLATS'] != own['SLATS']:
            unit_module.slats()
            #Передача команды ведомому срабатывает только один раз
            #Заодно и передается название проперти, которое надо подтянуть
            if own['statusDict']['listWingman'][0] != '1':
                if abs(own['localDict']['SLATS'] - own['SLATS']) == 2:
                    mechanicWingman = 'SLATS'
                    controlUnitWingman(own, mechanicWingman)
           
        #Фонарь кабины
        if own['localDict']['CANOPY'] != own['CANOPY']:
            unit_module.canopy()
            #Стандартная опреация для проигрывания звука
            if own['localDict']['CANOPY'] > own['CANOPY']:
                if own['localDict']['CANOPY'] == own['CANOPY'] + 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'GEAR'
                    audioUnit()
            if own['localDict']['CANOPY'] < own['CANOPY']:
                if own['localDict']['CANOPY'] == own['CANOPY'] - 2:
                    own['localDict']['audioLife'] = 100
                    own['localDict']['audioProp'] = 'GEAR'
                    audioUnit()
       
        ######################################################
        ######################################################
        ######################################################
       
        #Смена подвесок - одиночный вызов функции из скрипта ClassOperation, он изначально вызван в этом скрипте
        if own['localDict']['weapon'] != own['weapon']:
            own['PR'] = 0
            own['sbros'] = 0
            #Запускаем функцию работы по отсоединению подвесок и использованию оружия
            ClassOperation.weaponsUnit()
            #Корректируем значения словаря юнита
            own['localDict']['weapon'] = own['weapon']
           
        #Стрельба - одиночный вызов функции из скрипта ClassOperation, он изначально вызван в этом скрипте
        if own['localDict']['sbros'] != own['sbros']:
            if own['sbros'] == 1:
                ClassOperation.shooting()
            own['localDict']['sbros'] = own['sbros']
       
        #Это - определение целей при смене текущй цели или выборе типа прицеливания
        if own['localDict']['enemy'] != own['enemy'] or own['localDict']['targetType'] != own['targetType']:
            if len(own['localDict']['localTargetList']) > 0:
                try:
                    own['idTarget'] = own['localDict']['localTargetList'][own['enemy']]
                    if 'CameraPilot' in own.childrenRecursive:
                        own.childrenRecursive['CameraPilot']['idTarget'] = own['idTarget']
                except:
                    own['enemy'] = 0
                    own['idTarget'] = own['localDict']['localTargetList'][0]
                    #При ошибке убираем индекс из списка
                    #if own['idTarget'] in own['localDict']['localTargetList']:
                       #own['localDict']['localTargetList'].remove(str(id(target)))
                   
           
            own['localDict']['enemy'] = own['enemy']
            own['localDict']['targetType'] = own['targetType']

        #Сортировка списка угроз
        if own['timerThreat'] > 3.0:
            threatSorted()
           
       
        #Работа сенсоров - вызов функции из скрипта ClassSensor, он вызывается через разные промежутки времени, минимум раз в секунду
        #и только при "живом юните", время паузы между сканированием регулируется - оно задается при переключении сенсоров
        if own['crash'] > 0.1 and own['bot'] > -1:
            #if own['limTimerScan'] > own['scanTimer']:
               
                sensorUnit = own['localDict']['sensList'][own['typeSensor']]
                ClassSensor.SensorDef()

Для облегчения понимания я подчеркнул место своей логической ошибки. Получается, что при получении нулевого уровня исправности происходит повторный вызов списка сцены и требование убрать уже несуществующую! Причем эта операция осуществляется за один тик - в один проход функции. Во всяком случае, я такой вывод сделал, скажем так. Из этого следует вывод, что так быдлокодить нельзя. Питон, конечно терпелив и высвечивает программеру-кодеру его ошибки в консоли, но и у него иногда лопается терпение. Может быть, кто-то из гораздо более сведущих в программировании людей посчитает этот вылет багом самого Питона, в котором не выполняется исключение перехвата ошибок. Однако подобный прискорбный случай говорит и том, что никто, кроме меня, подобную ситуацию еще не создавал...
В общем, проблема была решена путем удаления лишнего обращения к функции. я жестко указал в коде, что операция по поиску и удалению оверлейной сцены должна происходить только при получении проперти crash = 0. И неважно, чем это вызвано - стокновением с землей, сбитием, столкновением с другим юнитом. Меняется проперти crash? Оно равно нулю? Вызываем функцию CrashModel. Только ОДИН раз, и в ней же проверяем - есть в потомках камера пилота? Тогда убираем оверлейную сцену. ВСЕ!
После переделки кода последовал запуск-тест. специально гробанул многострадального "кролика" подопытного МиГ-23 об землю. Выхожу из игрового режима. Все в норме. Еще раз! все чисто. Но, товарищи, так кодить нельзя.
правда, попутно еще выловил и устранил баг с пушкой, которую я сломал в ходе своих "оптимизаций" довольно давно, почистил код. К тому же, действуя по принципу: "Коль пошла такая пьянка, режь последний огурец", в очередной раз устроил погром в скрипте кабины. Собирался я это сделать давно, просто удачно под руку подвернулось. Многие оперции в кабинном скрипте стандартны, как и имена объектов сцены. к примеру, лампы индикации подвесок, закрылков, тормоза, шасси. Нет смысла из файла в файл плодить одни и те же функции с одинаковыми названиями и одинаковым до буквы кодом. Поэтому в игровом файле-стартере я создал еще один скрипт ClassCockpit, куда и сбросил все стандартные функции из кабинногог кода. Теперь в коде кабины работают только стрелки приборов да те вещи, которые уникальны для того или иного самолета. Например индикация топливной системы.
Буквально на днях Андрей предложил переделать скрипт загрузки сторонних бленд-файлов через асинхронизацию и дал образец-пример. Надеюсь, сумею разобраться и применить относительно своего файла, потому что собирался это делать в любом случае, к тому же в способе dron-a есть возможность вставить красивую картинку, а не черный плейн, как у меня. Будем продолжать движение.