Skip to content

Bring all ports together into one whole abstracted source tree #6

Description

@Voxel9

With more ports being added recently, it's only going to become more difficult to add new features and fixes due to each port pretty much having its own entirely separate source.

Things we should abstract:

  • graphics (a big one, but with a project of this size it shouldn't be too much of an issue. I kind of modelled my Xbox port in an abstract-y fashion that could be used as some sort of a base)
  • Audio
  • Input (need to consider that some ports have multiple methods of input and others only have one)

One way I'm quite familiar with already is having abstracted functions in one separate folder for each platform, so that game sources can include the appropriate abstracted functions for the platform being built for, through something like #define PLATFORM_XXX
(And there will still be separate Makefiles for each platform, so the specific folder with abstracted functions for that platform can be added easily)

Activity

  1. mandar1jn commented on Mar 22, 2021

    @mandar1jn

    If this is done the code would be a ton of pre-processor statements.

  2. modeco80 commented on Aug 24, 2021

    @modeco80

    If this is done the code would be a ton of pre-processor statements.

    Not true. There are multiple ways of writing platform independent code.
    Preprocessor soup in the source directly is definitely one way, and you're right in pointing out that it'd be klutzy.

    The way I would rather consider (as I agree, preprocessor soup sucks) is defining a kind of "trait concept", but at another weird level.

    There would be some preprocessor soup when including the decl headers, although it'd be a lot less annoying, AND - most importantly,
    it wouldn't directly clutter the rest of the game source with SDL2 or Win32 dependencies, only the implementation of the system and graphics contexts (which would be inside of another TU/compiler invocation)
    would be affected.

    This, in code form would basically be the expected interface.
    Templates/ConceptsLite could be used to assert this interface somehow (as basically all platforms have a GCC version either equal to or greater to 10, C++20 could be used without issue), however you probably wouldn't even need to do that.

    // Basically anything which needs some system header or whatever
    struct System {
    	// an audio clip.
    	struct AudioClip {
    	};
    
    	static AudioClip* LoadAudioClip(const std::string_view path);
    
    	static void PlayAudioClip(AudioClip* clip);
    
    	// Other methods...
    };
    
    // This actually would need to be instantiated,
    // but it handles graphics. 
    struct GraphicsContext {
    
    	// an interface for whatever's drawable.
    	struct Drawable {
    		virtual ~Drawable() = default;
    		virtual void Draw() const = 0;
    	};
    
    	// These are here as they would use context specific methods when drawing
    
    	struct Texture {
    		short GetWidth() const;
    		short GetHeight() const;
    		uint32_t* GetData() const;
    
    		// should Texture Is-A Drawable too?
    	// bits obvs...
    	};
    
    	struct Sprite : public Drawable {
    		Sprite(Texture* tex); // rect width/height members become { tex->GetWidth(), tex->GetHeight() } respectively
    
    		void Draw() const override;
    		void SetPosition(const Vector2s& pos);
    	private:
    		Texture* mTex{};
    		Rect<short> mRect;
    	};
    
    	// push an item onto the draw stack
    	void PushDrawStack(Drawable* drawable);
    
    	// pops the last item off the draw stack, removing it from the stack but not deleting it
    	// (therefore it still exists, as the managing class is what ultimately will end up deleting it).
    	// This function will return nullptr if no more items are in the draw stack.
    	Drawable* PopDrawStack();
    
    	// Clears the draw stack, rendering all items inside of it
    	// only called once per frame. When this returns, PresentFrame() can be called.
    	void RenderDrawStack();
    
    	// present the rendered frame.
    	void PresentFrame();
    };

    Anyways, the header declaring this stuff would add critical usings:

    	// Declare usings the rest of the game code assumes to exist
    	using System = Sdl2System;
    	using GraphicsContext = Sdl2GraphicsContext;

    Those two using's would allow any code to just be written ala:

    // hypothetical
    void Player::Draw() {
    	// Push my sprite into the draw stack
    	g_GraphicsContext.PushDrawStack(mPlayerSprite);
    }
    
    // Yet another hypothetical one
    void Player::GotCoin() {
    	System::PlayAudioClip(gAudioClips[AudioClips::GotCoin]);
    }
  3. mandar1jn commented on Aug 24, 2021

    @mandar1jn

    I like the way you think. I might actually give this a shot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions