ko4life-net / ko

Open source development of the game Knight Online. This is a reversed engineered old version of the game aiming to replicate the nostalgic experience we all once had <3
MIT License
52 stars 21 forks source link

Separate N3Base into a static library #173

Open stevewgr opened 1 year ago

stevewgr commented 1 year ago

Description

Note that if we separate N3Base into a static library, it will reduce build time noticeably (if done properly). At the moment every project includes N3Base's / Engine sources directly in every project. This means if one builds all projects, these sources will recompile per project. This is not ideal.

Making such a change will not be a quick process, hence this ticket is created to keep track of progress and discuss things as needed. The reason it's not a quick process, because in the existing codebase the game and engine mix up files quite noticeably or even duplicate them as in server files. Also there are preprocessors for tools (_N3TOOL), game (_N3GAME) and servers (_3DSERVER) within the engine sources. Therefore it may take some time and will be good to keep this in mind when either adding files or refactoring.

It also requires a bit of thinking on the architectural level. One idea would be to create in N3Base multiple targets with different configurations. Example:

flowchart
    subgraph Engine Targets
    N3Base-->N3BaseDebug
    N3Base-->N3BaseRelease
    N3BaseDebug-->N3BaseTool
    N3BaseRelease-->N3BaseTool
    N3BaseDebug-->N3BaseGame
    N3BaseRelease-->N3BaseGame
    N3BaseDebug-->N3BaseServer
    N3BaseRelease-->N3BaseServer
    end

    subgraph N3Base Consumer Projects
    N3BaseGame<-->KnightOnLine

    N3BaseTool-->N3ME
    N3BaseTool-->N3CE
    N3BaseTool-->N3FXE

    N3BaseServer-->AIServer
    N3BaseServer-->Aujard
    N3BaseServer-->Ebenezer
    N3BaseServer-->LoginServer
    end

N3Base target will compile all shared sources that do not require anything specific to tools, game or server. This means most of the engine sources will be compiled once.

Now if we compile one of the tools, since all tools depend on N3BaseTool, it will compile N3Base (if it was not already compiled) and compile the sources relevant for tools where some sources for example contains a Save function. Same situation for Server and Game.

The benefits of proper setup like this are:

  1. It will compile much faster (even though the current compile time we have is pretty fast and satisfactory)
  2. Keep projects clean, where it's easier to see which sources come from where
  3. Allow us to implement proper abstraction and generally clean up a lot of code duplication we currently have in this codebase

Configuring such a setup will probably be easier if we use some project generator tool, such as Meson, CMake or Premake.

twostars commented 4 months ago

What exactly do you consider N3BaseGame, etc to be here? Are they build configurations for N3Base, separate wrapper projects or what? I might be thinking of it too much from a strictly VS perspective, but that's really the only angle I can look at it from.

Here I just have a singular project for N3Base, with separate build configurations for essentially tools, and the client (or other client facing things which shouldn't have access to tool-like behaviour).

You include the server, but it's actually an interesting case because it's essentially only 3 files (N3ShapeMgr.cpp/.h & My_3DStruct.h), even though it's copied in both projects -- AND they're only used by AI and Ebenezer, because they both use maps.

Beyond merging those (if they aren't already merged in this repo), I have to wonder if it's even worth having a whole separate build configuration for it at that point. It might as well just reuse the client's implementation, but I don't see much of a reason to go out of the way to separate things for it -- it might as well just continue to compile directly without a separate lib.

Beyond that, the only special case I can think of -- at least in the original base code -- is the UI editor via _N3UIE, which wants access to the sound manager (whereas other tools supposedly don't). So it can just be considered part of the tools and they can all have access to it, really. (Edit: N3Indoor via _N3INDOOR is a special case too, which has implementation logic)

But getting back on track, I have build configurations for these (e.g. Tool-Debug, Tool-Release, Client-Debug, ...), and then associate them in the solution's configuration manager with the regular build (or you could just drop the generic Debug/Release configurations entirely for these projects and only use the appropriate 'Tool' configs, for example). There's a few classes I selectively don't compile into client builds (CN3EngTool for example, CN3PMeshCreate for another) based on the active build configuration and then I just include it appropriately.

While having more build configurations is a little bit annoying, if they're consistently named, you don't need to explicitly check them all; MSBuild can use property functions to simplify, e.g. Condition="$(Configuration.EndsWith('Release'))" (or you can just straight up setup properties for these based on the configurations upfront and then check those).

It does however complicate the official behaviour with some tools intending on using /MT[d] (runtime via lib) vs /MD[d] (runtime via DLL). In this respect, handling these via VS' build configurations becomes a little messy, so for the sake of some client facing stuff, I found it simpler to just prefer to make everything /MT[d] instead and not bother to mess around with further making that configurable.

If you're having to change all of these projects (and there's about 20 of them), I would also avidly recommend using property sheets for it. It will save a lot of time and reduce a lot of duplicate config.

Of course, if you're going to swap this & use some project generator tool for it, then I guess your approach changes quite drastically.