class _Logger(object):
    def __init__(self):
        pass

    @staticmethod
    def log(message, messageType=logLevelsEnum.debug):
        """


        @type message: str
        @type messageType: int
        """
        mod = inspect.getmodule(inspect.stack()[2][0])
        if mod:
            modName = mod.__name__
            del mod
        else:
            modName = 'root'

        logger = logging.getLogger(modName)

        if messageType == logLevelsEnum.debug:
            logger.debug(message)
        elif messageType == logLevelsEnum.info:
            logger.info(message)
        elif messageType == logLevelsEnum.warning:
            logger.warning(message)
        else:  # logLevelsEnum.error:
            logger.error(message) class logLevelsEnum(object):
    debug = 0
    info = 1
    warning = 2
    error = 3 class BaseBackend(object):
    __metaclass__ = ABCMeta

    def __init__(self):
        self._shaders = None
        self._textures = None
        self._poliCount = 0
        self._culling = False

    def renderMeshes(self, drawingData):
        pass

    def resize(self, size):
        pass

    def drawAll(self, drawingData):
        pass

    def drawSkybox(self, sky):
        pass

    def switchMultiSample(self, value):
        pass

    def terminate(self):
        pass

    def getShadersManager(self):
        pass

    @staticmethod
    def getRenderTarget():
        pass

    def _getPoliCount(self):
        return self._poliCount

    poligonsDrawnThisUpdate = property(_getPoliCount)

    @staticmethod
    def _getMaxColorAttachments():
        pass

    def setContextState(self):
        pass

    @property
    def culling(self):
        return self._culling

    @culling.setter
    def culling(self, value):
        pass

    @staticmethod
    def create2DTexture(ID, mipmapsNumber, pix, w, h, repeat):
        pass

    def update2DTexture(self, ID, data, fromTuple, toTuple):
        pass

    def getBackBufferContent(self, w, h, destBuffer):
        pass class EventsManager(object):
    def __init__(self):
    def addListener(self, ID, listener):
    def removeListener(self, ID):
    def _announce(self, event):
    def isKeyPressed(self, keyName): class ScenesManager(object):
    def __init__(self):
        self._scenes = {}
        self._models = None
        self.sounds = None
        self._engine = None
        self.currentSceneID = ""

        self._defaultScene = Scene

    def initialize(self, engine):
        self._models = engine.models
        self.sounds = engine.sounds
        self._engine = engine
        self._defaultScene = Scene('DefaultScene', engine, resolution=160, gravity=-9.8)
        self._scenes[self._defaultScene.ID] = self._defaultScene

    def _get_idExists(self, ID):
        return ID in self._scenes.keys()

    idExists = property(fget=_get_idExists)

    def addScene(self, ID, resolution=160, gravity=-9.8):
        if ID in self._scenes.keys():
            raise KeyError("The ID exists already.")
        else:
            ns = Scene(ID, self._engine, gravity, resolution)
            self._scenes[ID] = ns
            return ns

    def removeScene(self, id):
        if self.currentSceneID == id:
            self.currentSceneID = ""
        self._scenes.pop(id)

    def getScene(self, id):
        """

        @rtype : Scene
        """
        return self._scenes.get(id, self._defaultScene)

    def setCurrentSceneID(self, ID):
        if ID not in self._scenes.keys():
            raise KeyError("The ID does not exist")
        else:
            self.currentSceneID = ID

    def setCurrentScene(self, scene):
        if not scene:
            scene = self._defaultScene
        self.currentSceneID = scene.ID

    def getCurrentScene(self):
        """

        @rtype : Scene
        """
        return self.getScene(self.currentSceneID)

    currentScene = property(getCurrentScene, setCurrentScene)

    def update(self, netElapsedTime, windowSize):
        currentScene = self.currentScene
        if currentScene:
            return currentScene.update(netElapsedTime, windowSize)
        else:
            self.currentScene = self._defaultScene
            return DrawingData()
            
    def terminate(self):
        print('scenes man terminate not implemented.')
        # for scene in self._scenes ... terminate? class TexturesManager(BaseManager):
    textureLoaded = 'textureLoaded'

    def __init__(self):
        # ThreadedSystem.__init__(self)
        super(TexturesManager, self).__init__()
        self._textureCache = {}
        self._cubeTexCache = {}
        self._context = None
        self._window = None
        self._defaultTexture = None
        self._defaultNormalMap = None
        self._engine = None

    def initialize(self, engine):
        self._engine = engine
        self.remotequeue = Queue()
        self.localqueue = Queue()
        # self.parallelProcess = Process(target=serve, args=[self.remotequeue, self.localqueue])
        # self.parallelProcess.start()

        dt = os.path.join(self._engine.path.defaults.textures, "default.png")
        dtnm = os.path.join(self._engine.path.defaults.textures, "default_nm.png")
        try:
            self.loadTexture(dt, "default", serial=True, raiseOnError=True)
            self._defaultTexture = self._textureCache.get('default')
            self.loadTexture(dtnm, "_defaultNM", serial=True, raiseOnError=True)
            self._defaultNormalMap = self._textureCache.get('_defaultNM')
        except Exception:
            raise

    def run(self):
        # if SDL_GL_MakeCurrent(self._window, self._context):
        #     raise RuntimeError(SDL_GetError())
        while self._running:
            sleep(1.5)
            self.checkQueue()

    def getDefaultNormalMap(self):
        return self._defaultNormalMap

    def checkQueue(self):
        # return
        try:
            if not self.localqueue._closed:
                # remoteID, ttype, taskID, args = self.localqueue.get(False, 1)
                remoteID, ttype, taskID, args = self.localqueue.get_nowait()
                if ttype == messageType.ready:
                    self._fillTexture(args)
                elif ttype == messageType.exception:
                    raise RuntimeError(*args)
        except Empty:
            pass

    def createEmpty2DTexture(self, ID, width, height):
        self._engine.log('Using untested createEmpty2DTexture', logLevelsEnum.warning)
        if self.exists(ID):
            raise RuntimeError('the ID is already in use.')
        tex = self._engine.backend.create2DTexture(ID, -1, None, width, height)
        self._textureCache[ID] = tex

    def update2DTexture(self, ID, data, fromTuple, toTuple):
        self._engine.backend.update2DTexture(ID, data, fromTuple, toTuple)

    def _fillTexture(self, args):   # todo: move to base backend
        pix, w, h, ID, mipmapsNumber, repeat = args
        try:
            tex = self._engine.backend.create2DTexture(ID, mipmapsNumber, pix, w, h, repeat)
            self._textureCache[ID] = tex
        except Exception as ex:
            self._engine.log('Error loading texture \'{0}\':\n\t{1}\n'
                             'Using default texture.'.format(ID, str(ex)))
            if self._defaultTexture is None:
                raise
            self._textureCache[ID] = self._defaultTexture

        self._engine.postEvent(textureLoadedEvent(ID))

    def loadTexture(self, filePath, ID, mipmapsNumber=10, serial=True, raiseOnError=False, repeat=True, force=False):
        """
        Load texture 'filename' as 'ID'. If 'serial', loading will be done secuentially, so
        this function won't return until the load is finished.
        If 'raiseOnError' is True, any error will raise an Exception. Otherwise, the default
        texture will be returned.
        @param raiseOnError:
        @type raiseOnError:bool
        @type ID: str
        @type filePath: str
        @rtype : None
        @param filePath:
        @param ID:
        """
        warn('forcing serial texture load')
        serial = True
        filePath = os.path.abspath(filePath)
        tex = self._textureCache.get(ID)
        getdefault = False
        if tex is None or force:
            if not os.path.exists(filePath):
                defaultTexturesDir = os.path.join(self._engine.path, 'defaults', 'textures')
                filename = os.path.basename(filePath)
                defaultedpath = os.path.join(defaultTexturesDir, filename)
                if not os.path.exists(defaultedpath):
                    getdefault = True
                    if raiseOnError:
                        raise RuntimeError('File not Found', filePath)
                    else:
                        self._engine.log('Error loading texture \'{0}\'\n'
                                   '\tUsing default texture.'.format(filePath), logLevelsEnum.error)
                else:
                    self._engine.log('Error loading texture \'{0}\'\n'
                               '\tUsing texture found at: {1}'.format(filePath, defaultedpath), logLevelsEnum.error)
                    filePath = defaultedpath

            if not getdefault:
                if serial:
                    pix, w, h = TexturesManagerServer.getPILpixels(filePath)
                    self._fillTexture([pix, w, h, ID, mipmapsNumber, repeat])
                else:
                    self.remotequeue.put_nowait(('loadTexture', [filePath, ID, mipmapsNumber, repeat]))
            else:
                if self._defaultTexture is None:
                    raise AttributeError("Fatal error: Default texture not defined.")
                else:
                    tex = self._defaultTexture
                self._textureCache[ID] = tex

    def loadCubeTexture(self, folderPath, ID):
        """

        @type ID: str
        @type folderPath: str
        @rtype : None
        @param ID:
        """
        # TODO: turn into parallel
        cube = self._cubeTexCache.get(ID)
        if not cube:
            if not os.path.exists(folderPath):
            #     folderPath = self._engine.io.findPath(folderPath)
            # if not folderPath:
                self._engine.log('Error loading cube texture {0}:\n{1}'.format(folderPath, 'Folder not found.'), logLevelsEnum.error)

            cube = CubeTexture(self._engine, ID)
            cube.loadFromFolder(folderPath, TexturesManagerServer.getPILpixels)

        return cube

    def exists(self, texID):
        return texID in self._textureCache

    def existsCube(self, cubeID):
        return cubeID in self._cubeTexCache

    def getTexture(self, ID):
        return self._textureCache.get(ID, self._defaultTexture)

    def getCubeTexture(self, ID):
        return self._cubeTexCache.get(ID)

    def _addTextureIDs(self, IDs):
        for a, b in IDs:
            if a not in self._textureCache.keys():
                self._textureCache[a] = b
            else:
                raise AttributeError('Texture ID \'{}\' already exist.'.format(a))

    def getDefaultTexture(self):
        return self._defaultTexture

    def terminate(self):
        try:
            pass
            # self.remotequeue.put_nowait(('close', []))
            # self.remotequeue.close()
        except BrokenPipeError:
            pass

        # self.parallelProcess.terminate()

        # TODO: Move following code to backend
        # glBindTexture(GL_TEXTURE_2D, 0)
        # for t in self._textureCache.values():
        #
        #     glDeleteTextures(1, np.array([t], np.int32))
        #
        # for t in self._cubeTexCache.values():
        #     glDeleteTextures(1, np.array([t], np.int32))
        #
        # # SDL_GL_DeleteContext(self._context) class Window(Window_Base):
    """
    Class for starting an SDL2 based Engendro3D Window.

    """

    def _createInternalWindow(self, title, engine, fullscreen):
        flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE
        if fullscreen:
            flags = flags | SDL_WINDOW_FULLSCREEN

        try:
            title = title.encode()
        except Exception:
            pass
        self._SDL_Window = SDL_CreateWindow(title, 0, 0, self._size[0], self._size[1], flags)
        if not self._SDL_Window:
            sdlerr = SDL_GetError()
            msg = u'Error creating window for \'{}\': {}'.format(self.gameName, sdlerr)
            self._engine.log(msg, log)
            raise Exception(msg)
        _, self._context = engine._getNewContext(self._SDL_Window)

        SDL_GL_MakeCurrent(self._SDL_Window, self._context)

    @property
    def windowID(self):
        return SDL_GetWindowID(self._SDL_Window)

    def setFullScreen(self, setfull):
        if setfull:
            f = SDL_WINDOW_FULLSCREEN
        else:
            f = 0
        # todo: resize if size != fullscreensize

        SDL_SetWindowFullscreen(self._SDL_Window, f)
        self._isFull = setfull

    def _getMouseMode(self):
        return SDL_GetRelativeMouseMode()

    def _setMouseMode(self, value):
        # todo: move cursor to window center before changing this
        SDL_SetRelativeMouseMode(value)

    mouseLock = property(_getMouseMode, _setMouseMode)

    @property
    def gamma(self):
        return SDL_GetWindowBrightness(self._SDL_Window)

    @gamma.setter
    def gamma(self, value):
        """
        Set int value for this window's gamma.
        @type vakue: int
        # SDL_SetWindowBrightness
        """
        if (not isinstance(value, int) and not isinstance(value, float)) or value <= 0:
            raise ValueError('Gamma value must be a float > 0.')
        if SDL_SetWindowBrightness(self._SDL_Window, value) != 0:
            raise Exception('Error setting Gamma: ' + SDL_GetError())

    @property
    def size(self):
        w = ct.pointer(ct.c_int(0))
        h = ct.pointer(ct.c_int(0))
        SDL_GetWindowSize(self._SDL_Window, w, h)

        return w.contents.value, h.contents.value

    @size.setter
    def size(self, val):
        self._previousSize = self.size
        w, h = val
        if not self._isFull:
            SDL_SetWindowSize(self._SDL_Window, ct.c_int(w), ct.c_int(h))
        else:
            SDL_SetWindowDisplayMode(_SDL_Window, SDL_DisplayMode(w, h))
            self._fullscreenSize = (w, h)
        self._sizeChanged(w, h)

    @property
    def title(self):
        """

        @rtype : str
        """
        t = SDL_GetWindowTitle(self._SDL_Window)
        return t

    @title.setter
    def title(self, value):
        """

        @type value: str
        """
        try:
            value = value.encode()
        except Exception:
            pass
        SDL_SetWindowTitle(self._SDL_Window, value)

    @property
    def vsynch(self):
        ret = SDL_GL_GetSwapInterval()
        if ret != 0:
            return True
        else:
            return False

    @vsynch.setter
    def vsynch(self, val):
        if bool(val):
            if SDL_GL_SetSwapInterval(-1) == -1:
                SDL_GL_SetSwapInterval(1)
        else:
            SDL_GL_SetSwapInterval(0)

    def setIcon(self, path):
        aPath = os.path.abspath(path)
        if not os.path.exists(aPath):
            self._engine.log('Window.setIcon error: File {} not found. Skipping.'.format(aPath))
            return
        try:
            from PIL import Image
            im = Image.open(aPath)
            w, h = im.size[0], im.size[1]
            if im.mode == 'RGB':
                alphamask = 0
                depth = 24
            else:
                alphamask = 0xFF000000
                depth = 32
            try:
                pix = im.tobytes()
                im.close()
            except Exception:
                raise

            surface = SDL_CreateRGBSurfaceFrom(pix, w, h, depth, w * 4, 0x000000FF, 0x0000FF00, 0x00FF0000, alphamask)
            SDL_SetWindowIcon(self._SDL_Window, surface)
            SDL_FreeSurface(surface)

        except Exception as ex:
            self._engine.log('Window.setIcon error: ' + str(ex), logLevelsEnum.debug)
            raise

    def getMultiSampleNumber(self):
        # res = ct.pointer(ct.c_long(0))
        # if not SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, res):
        #     return res.contents.value
        # else:
        #     return 0
        print('Not implemented.')
        return -1

    def _pollEvents(self):
        event = SDL_Event()
        while SDL_PollEvent(event):
            if event.type == SDL_QUIT:
                self.close()
            elif event.type == SDL_WINDOWEVENT:
                winev = event.window.event
                evWinId = event.window.windowID
                if evWinId == self.windowID:
                    if winev == SDL_WINDOWEVENT_RESIZED:
                        self._sizeChanged(event.window.data1, event.window.data2)
                    if winev == SDL_WINDOWEVENT_CLOSE:
                        self.close()
            self.events._announce(event)

    def _makeContextCurrent(self):
        SDL_GL_MakeCurrent(self._SDL_Window, self._context)

    def _performSwap(self):
        SDL_GL_SwapWindow(self._SDL_Window)

    def close(self):
        SDL_GL_DeleteContext(self._context)
        SDL_DestroyWindow(self._SDL_Window)
        Window_Base.close(self)

    def getCurrentDPIs(self):
        h = ct.pointer(ct.c_float(0))
        v = ct.pointer(ct.c_float(0))
        d = ct.pointer(ct.c_float(0))
        SDL_GetDisplayDPI(0, d, h, v)  # fixme: should consider real monitor index
        return int(h.contents.value), int(v.contents.value)

    def saveScreenShot(self, filePath=''):
        def slugify(value):
            """
            Based on https://github.com/django/django/blob/master/django/utils/text.py#L413

            Copyright (c) Django Software Foundation and individual contributors.
            All rights reserved.

            Redistribution and use in source and binary forms, with or without modification,
            are permitted provided that the following conditions are met:

                1. Redistributions of source code must retain the above copyright notice,
                   this list of conditions and the following disclaimer.

                2. Redistributions in binary form must reproduce the above copyright
                   notice, this list of conditions and the following disclaimer in the
                   documentation and/or other materials provided with the distribution.

                3. Neither the name of Django nor the names of its contributors may be used
                   to endorse or promote products derived from this software without
                   specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
            WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
            DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
            ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
            (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
            LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
            ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
            SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            """
            import unicodedata, re
            value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore').decode('ascii')
            value = re.sub(r'[^\w\s-]', '', value).strip().lower()

            return value

        w, h = self.size

        import numpy as np
        dest = np.empty(w * h * 3, np.uint8)
        self.backend.getBackBufferContent(w, h, dest)
        # Now the file creation
        if filePath == '':
            from datetime import datetime
            ind = 0
            basePath = os.path.abspath(os.curdir)
            now = datetime.now()
            date = str(datetime.date(now))
            timeNow = datetime.time(now)
            time = '{}-{}-{}'.format(timeNow.hour, timeNow.minute, timeNow.second)
            baseName = slugify(self.gameName) + '_' + date + '_' + time
            filePath = os.path.join(basePath, baseName)
            filePath = filePath + '.png'
            while os.path.exists(filePath):
                filePath = os.path.join(basePath, baseName + str(ind))
                filePath = filePath + '.png'
                ind += 1

        from PIL.Image import fromarray, merge
        from PIL.ImageOps import flip
        image = fromarray(dest.reshape(h, w, 3))
        image = flip(image)
        b, g, r = image.split()
        image = merge("RGB", (r, g, b))
        image.save(filePath) class ThreadingManager:
    def __init__(self):
        self._maxThreads = 0
    #     TODO: Reimplement multiprocess here.

    def initialize(self, maxThreads):
        self._maxThreads = maxThreads

    def map(self, func, iter):
        return map(func, iter)

    def terminate(self):
        pass class PluginsManager(BaseManager):
    def __init__(self):
        super(PluginsManager, self).__init__()
        self._pluginPaths = {}
        self._tempPluginPaths = {}
        self._enabled = {}
        self._plugins = {}

    def addPlugin(self, ID, pluginPath, isEnabled=True):
        if ID in self._pluginPaths.keys():
            raise RuntimeError('the specified ID ({}) already exist.'.format(ID))
        self._pluginPaths[ID] = pluginPath
        self._enabled[ID] = isEnabled
        self._plugins[ID] = self._injectPlugin(ID, pluginPath)

    def removePlugin(self, ID):
        if ID not in self._pluginPaths.keys():
            raise RuntimeError('the specified ID ({}) does not exist.'.format(ID))
        self._enabled.pop(ID)
        self._pluginPaths.pop(ID)
        self._plugins.pop(ID)

    def setPluginEnableState(self, ID, stateBool):
        if ID not in self._pluginPaths.keys():
            raise RuntimeError('the specified ID ({}) does not exist.'.format(ID))
        self._enabled[ID] = bool(stateBool)

    def _injectPlugin(self, ID, pluginPath):
        tempDir = mkdtemp(prefix='e3d_' + ID + '_')
        self._tempPluginPaths[ID] = tempDir
        unpack_archive(pluginPath, tempDir, 'gztar')
        plugDesc = PluginDescription.fromDisk(tempDir)
        module_name = plugDesc.name.lower().replace(' ', '_')
        mainFilePath = path.join(tempDir, MAINFILENAME)
        plugin_module = self._loadModule(mainFilePath, module_name)

        mainClass = getattr(plugin_module, MAINCLASSNAME)(self._engine)
        if not issubclass(type(mainClass), Runnable):
            raise TypeError('main class of plugin \'{}\' must inherith from Runnable'.format(plugDesc.name))

        data = {'name': ID, 'path': pluginPath}
        mainClass.preparePlugin(data)

        return _Plugin(plugDesc, mainClass, pluginPath)

    def _loadModule(self, mainFilePath, module_name):
        if version_info[0] == 2:
            plugin_module = imp.load_source(module_name, mainFilePath)
        else:
            if version_info[1] < 5:
                plugin_module = SourceFileLoader(module_name, mainFilePath).load_module()
            else:
                spec = util.spec_from_file_location(module_name, mainFilePath)
                plugin_module = util.module_from_spec(spec)
                spec.loader.exec_module(plugin_module)
        return plugin_module

    def preUpdatePlugins(self):
        for p in self._plugins.values():
            p.mainClass.onPreUpdate()

    def postUpdatePlugins(self):
        for p in self._plugins.values():
            p.mainClass.onPostUpdate()

    def terminate(self):
        for ID, p in self._plugins.items():
            try:
                p.mainClass.terminate()
            except Exception as ex:
                self._engine.log('plugin \'{}\' failed to terminate:\n\t{}'.format(p.description.name, str(ex)))
            try:
                rmtree(self._tempPluginPaths[ID])
            except Exception as ex:
                self._engine.log('error removing plugin \'{}\' folder:\n\t{}'.format(p.description.name, str(ex))) class VideoManager(BaseManager):
    def __init__(self):
        self._videos = []
        super(VideoManager, self).__init__()

    def load(self, filePath, resizeTo=None):
        video = Video(self._engine, filePath, resizeTo)
        self._videos.append(video)
        return video

    def terminate(self):
        for video in self._videos:
            video.terminate()

    def update(self):
        for video in self._videos:
            video.update()

    def getVideoInfo(self, filePath):
        infos = ffmpeg_parse_infos(filePath)
        return infos
from os import path
from sdl2 import *
from ctypes import ArgumentError
from glaze.GL import loadGL, glGetIntegerv, glGetStringi, glGetString, GL_NUM_SHADING_LANGUAGE_VERSIONS, \
    GL_SHADING_LANGUAGE_VERSION, \
    GL_VERSION, GL_NUM_EXTENSIONS, GL_EXTENSIONS, glEnable, GL_MULTISAMPLE
from .Logging import _Logger, logLevelsEnum
from .backends.base_backend import BaseBackend
from .events_processing.EventsManagerClass import Event, EventsManager
from .scene_management.ScenesManagerClass import ScenesManager
from .texture_management.TextureManagerClass import TexturesManager
from .windowing.sdl_window import Window
from .ThreadingManagerClass import ThreadingManager
from .plugin_management.PluginsManagerClass import PluginsManager
from .video import VideoManager
        from .model_management.ModelsManagerClass import ModelsManager
        from .sound_management.SoundsManagerClass import SoundsManager
            from warnings import warn
        from .windowing.qt_window import e3DGLWidget
        from PySide.QtOpenGL import QGLWidget, QGLFormat
        import numpy as np




class globalsStruct:
    oglversionraw = None
    oglmajor = None
    oglminor = None
    glslmajor = None
    glslminor = None
    dummyContext = None
    dummyWindow = None
    glslall = None
    multisample = False


class Engine:
    def __init__(self, backend, multiSampleLevel=0, maxContext=(2, 1), useQT=False, useDebug=False):
        self._logger = _Logger()
        if not useQT:
            # Init SDL>>>>>>>>>>>>>>>>>>>>>>>>>
            if SDL_Init(SDL_INIT_EVERYTHING) != 0:
                sdlerr = self.getSDLError()
                self.log('Error on SDL init: ' + sdlerr, logLevelsEnum.error)
                raise Exception('Error on SDL init: ' + sdlerr)

        self.maxContext = maxContext
        assert issubclass(backend, BaseBackend)
        self.backend = backend
        self.path = EngineRoot((path.dirname(__file__),))

        self.events = EventsManager()
        self.textures = TexturesManager()
        self.scenes = ScenesManager()
        self.videos = VideoManager()
        self.threading = ThreadingManager()

        self.plugins = PluginsManager()