aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--design.tex202
-rw-r--r--glossary.bib4
-rw-r--r--img/activity-scripts.puml21
-rw-r--r--img/class-config.puml14
-rw-r--r--img/class-savemgr.puml13
-rw-r--r--img/class-scripts.puml7
-rw-r--r--img/poc-log.pngbin0 -> 12526 bytes
-rw-r--r--img/poc-output-scripts.pngbin0 -> 31761 bytes
-rw-r--r--img/theme.ipuml14
-rw-r--r--projdoc.cls2
-rw-r--r--reqs.toml65
-rw-r--r--time.txt1
12 files changed, 307 insertions, 36 deletions
diff --git a/design.tex b/design.tex
index 88d7cc8..4c4794d 100644
--- a/design.tex
+++ b/design.tex
@@ -115,9 +115,9 @@ the main part of the \gls{ecs}. The design of these eight systems in combination
\section{Design}
-\subsection{Rendering}
+% \subsection{Rendering}
-\subsection{Physics}
+% \subsection{Physics}
\subsection{Scripting}
@@ -154,6 +154,7 @@ architecture:\noparbreak
\end{itemize}
\subsubsection{Architecture}
+\label{sec:scripts:architecture}
The restrictions detailed at the start of this section are mitigated as
follows:\noparbreak
@@ -176,10 +177,11 @@ follows:\noparbreak
contains the following classes:\noparbreak
\begin{description}
\item[Script] This is the script \emph{interface}, and is used by the game
- programmer to create derived script classes. All methods in this class are
- declared virtual and have an empty implementation.
+ programmer to create derived script classes. All virtual methods in this class
+ have an empty implementation by default, and are optionally implemented by the
+ game programmer.
- This class' methods are protected by default, and a friend relation to
+ This class' virtual methods are protected by default, and a friend relation to
\codeinline{ScriptSystem} is used to ensure only \codeinline{ScriptSystem} is
able to call these methods.
@@ -188,6 +190,10 @@ contains the following classes:\noparbreak
function returns a reference to the \codeinline{BehaviorScript} instance it was
called on so it can be chained after the call to
\codeinline{GameObject::add_component}.
+
+ \codeinline{Script} also has a reference to its parent
+ \codeinline{BehaviorScript} instance so components can easily be retrieved using
+ the component manager.
\item[BehaviorScript]
This is the script \emph{component}, and is given as the template parameter to
\codeinline{GameObject::add_component}.
@@ -195,7 +201,8 @@ contains the following classes:\noparbreak
This class also uses a friend relation to \codeinline{ScriptSystem} to restrict
access to its private reference member \codeinline{script}.
\item[ScriptSystem] This is the system class that runs the methods implemented in
- the derivative instances of \codeinline{Script}.
+ the derivative instances of \codeinline{Script}. Described further in
+ \cref{sec:scripts:sytem}.
\end{description}
\begin{figure}
@@ -205,11 +212,37 @@ contains the following classes:\noparbreak
\label{fig:class-scripts}
\end{figure}
+\subsubsection{System}
+\label{sec:scripts:sytem}
+
+Because most of the complexity in the scripting interface comes from the containers
+described in \cref{sec:scripts:architecture}, the script system class itself is
+relatively simple. The script system provides a method
+\codeinline{ScriptSystem::update} that calls all active script's update functions.
+
+Because of the limitation that types cannot be passed as parameters in C++, the
+user-defined script class (derived from \codeinline{Script}) can not directly be
+instantiated when adding the component to the component manager. To work around this
+limitation, the method \codeinline{BehaviorScript::set_script} was created. This
+results in the possibility that an instance of \codeinline{BehaviorScript} does not
+reference an instance of \codeinline{Script}. In addition to the non-active script
+components, the script system skips over these `invalid' instances. This is
+illustrated in \cref{fig:activity-scripts}.
+
+\begin{figure}
+ \centering
+ \includepumldiag{img/activity-scripts.puml}
+ \caption{Script system update method}
+ \label{fig:activity-scripts}
+\end{figure}
+
+A \gls{poc} for the script system is shown in \cref{poc:scripts}.
+
\subsection{Audio}
Since writing a custom real-time audio mixing engine is outside the scope of this
project\mref and C++ does not provide a built-in cross-platform audio \gls{api}, the
-audio system inside the cr\^epe engine is implemented as a fa\c{c}ade around an
+audio system inside the cr\^epe engine is implemented as a \gls{facade} around an
existing audio library.
\subsubsection{Libraries}
@@ -220,15 +253,7 @@ searching for libraries (search terms: `dynamic/adaptive audio', `real-time audi
`audio library', `game audio engine'), several libraries were found. These libraries
were checked against the audio engine requirements \autocite{crepe:requirements} and
then tested by writing the same benchmark-style \gls{poc} using the remaining
-qualifying libraries:\noparbreak
-\begin{enumerate}
- \item Load a background track (Ogg Vorbis)
- \item Load three short samples (WAV)
- \item Start the background track
- \item Play each sample sequentially while pausing and resuming the background track
- \item Play all samples simultaniously
- \item Stop all audio and exit
-\end{enumerate}
+qualifying libraries. These \glspl{poc} are detailed in \cref{poc:audio}.
Of these libraries the following were determined to be unsuitable for use in this
project:\noparbreak
@@ -244,12 +269,12 @@ project:\noparbreak
The only library that remained after these tests is SoLoud \autocite{lib:soloud}. It
is Zlib/LibPng licensed and provides a high-level object-oriented C++ \gls{api}.
-\Cref{sec:audio:architecture} describes the fa\c{c}ade written for this library.
+\Cref{sec:audio:architecture} describes the \gls{facade} written for this library.
\subsubsection{Architecture}
\label{sec:audio:architecture}
-\Cref{fig:class-audio-facade} shows a class diagram of the audio fa\c{c}ade. It
+\Cref{fig:class-audio-facade} shows a class diagram of the audio \gls{facade}. It
contains the following classes:
\begin{description}
\item[SoundContext] This is a wrapper around the \codeinline{SoLoud::soloud}
@@ -265,17 +290,148 @@ contains the following classes:
\begin{figure}
\centering
\includepumldiag{img/facade-audio.puml}
- \caption{Audio fa\c{c}ade class diagram}
+ \caption{Audio \glsfmtshort{facade} class diagram}
\label{fig:class-audio-facade}
\end{figure}
-\subsection{Input}
+A \gls{poc} for the final Audio \gls{facade} is also showcased in \cref{poc:audio}.
+
+% \subsection{Save manager}
+%
+% The save manager \gls{api} is designed to give the game programmer an easy to use
+% interface for retrieving and storing game-specific data (\cref{req:savemgr}).
+%
+% Because the engine validation app only stores statistics and highscores, the save
+% manager is not required to support loading different save files
+% (\cref{req:savemgr:multi-file}), nor storing complicated data types
+% (\cref{req:savemgr:types-custom}). The save manager only supports storing simple
+% types (\cref{req:savemgr:types-scalar,req:savemgr:types-string}).
+%
+% In order to reduce complexity for the game programmer further, the following
+% requirements were also set:\noparbreak
+%
+% \begin{itemize}
+% \item Prevent data loss in the case of crashes (\cref{req:savemgr:journalling})
+% \item Handle opening/closing/flushing of the underlying file automatically
+% (\cref{req:savemgr:file-manage})
+% \item Save file variables are uniquely identified (\cref{req:savemgr:var-key})
+% \end{itemize}
+%
+% \subsubsection{Architecture}
+% \label{sec:savemgr:architecture}
+%
+% \begin{figure}
+% \centering
+% \includepumldiag{img/class-savemgr.puml}
+% \caption{Save manager class diagram}
+% \label{fig:class-savemgr}
+% \end{figure}
+%
+% In order to realize \cref{req:savemgr:journalling,req:savemgr:var-key}, a third-party
+% key-value database library is used.
+
+\subsection{Global configuration interface}
+
+Because the game programmer only has access to interfaces within the public \gls{api}
+namespace (\codeinline{crepe::api}), they would not be able to configure aspects of
+engine-internal components. To work around this access restriction, a global
+interface was made that stores arbitrary data, which can be accessed both internally
+and via the public \gls{api}.
+
+\subsubsection{Architecture}
+\label{sec:config:architecture}
+
+The global configuration interface consists of a single singleton class that can be
+accessed globally (see \cref{fig:class-config}). This class holds several anonymous
+structs, which are used to organize options per system or engine component.
+
+\begin{figure}
+ \centering
+ \includepumldiag{img/class-config.puml}
+ \caption{Global configuration interface class diagram}
+ \label{fig:class-config}
+\end{figure}
+
+% \subsection{Input}
+
+% \subsection{Physics}
+
+\appendix
+
+\section{\Glsfmtlongpl{poc}}
+
+The full (documented) source code of these \glspl{poc} is available on GitHub
+\autocite{crepe:code-repo}.
-\subsection{Physics}
+\subsection{Script system}
+\label{poc:scripts}
-\section{Tools}
+The script system \gls{poc} \autocite[script example]{crepe:code-repo} consists of
+the following:\noparbreak
+\begin{itemize}
+ \item A user-defined class (\codeinline{MyScript}) derived from
+ \codeinline{Script}, which only implements the \codeinline{update()} function.
+ \item A main function that---
+ \begin{itemize}
+ \item Creates a game object with \codeinline{Transform} and
+ \codeinline{BehaviorScript} components.
+ \item A call to \codeinline{ScriptSystem::update}, which results in
+ \codeinline{MyScript::update} being called.
+ \end{itemize}
+\end{itemize}
+
+Running the \gls{poc} results in the output shown in \cref{fig:poc-output-scripts},
+demonstrating that the system works as intended.
+
+\begin{figure}
+ \centering
+ \fitimg{\includegraphics[scale=0.7]{img/poc-output-scripts.png}}
+ \caption{Script system \glsfmtshort{poc} output}
+ \label{fig:poc-output-scripts}
+\end{figure}
+
+\subsection{Logging utilities}
+\label{poc:log}
+
+A small \gls{poc} was written to test the engine's logging functions \autocite[log
+example]{crepe:code-repo}. The following calls are used in this example:
+
+\begin{blockcode}
+dbg_trace(); // the dbg_* macros automatically show
+dbg_logf("test printf parameters: %d", 3); // where the message is coming from
+logf(LogLevel::INFO, "info message");
+logf(LogLevel::WARNING, "warning");
+logf(LogLevel::ERROR, "error");
+\end{blockcode}
+
+The output of this test is shown in \cref{fig:poc-log}.
+
+\begin{figure}
+ \centering
+ \includegraphics[scale=0.7]{img/poc-log.png}
+ \caption{Logging function outputs}
+ \label{fig:poc-log}
+\end{figure}
+
+\subsection{Audio}
+\label{poc:audio}
+
+A test that consists of the following steps was written for each audio library
+mentioned in \cref{sec:audio:libs}:\noparbreak
+\begin{enumerate}
+ \item Load a background track (Ogg Vorbis)
+ \item Load three short samples (WAV)
+ \item Start the background track
+ \item Play each sample sequentially while pausing and resuming the background track
+ \item Play all samples simultaniously
+ \item Stop all audio and exit
+\end{enumerate}
-\section{Conclusion}
+The repository \autocite{crepe:code-repo} contains two finished \glspl{poc} under the
+\codeinline{mwe/audio/} subdirectory for miniaudio and SoLoud. The SoLoud \gls{poc}
+was later converted to a full audio \gls{facade}, which is currently part of the
+cr\^epe engine. The \gls{poc} using the audio \gls{facade} is available from the same
+repository, under the \codeinline{src/example/audio_internal.cpp} file.
\end{document}
diff --git a/glossary.bib b/glossary.bib
index fda634f..0842ff8 100644
--- a/glossary.bib
+++ b/glossary.bib
@@ -51,4 +51,8 @@
long = {proof-of-concept},
}
+@entry{facade,
+ name = {fa\c{c}ade},
+ description = {Design pattern used to provide an abstraction from other software},
+}
diff --git a/img/activity-scripts.puml b/img/activity-scripts.puml
new file mode 100644
index 0000000..b833bdf
--- /dev/null
+++ b/img/activity-scripts.puml
@@ -0,0 +1,21 @@
+@startuml
+!include theme.ipuml
+
+start
+
+label continue
+:Get list of ""BehaviorScript"" components from ""ComponentManager"";
+
+while (for each ""BehaviorScript"" in list)
+ if (""BehaviorScript"" is active) then (yes)
+ if (""BehaviorScript"" has an instance of ""Script"") then (yes)
+ :Call update function of ""BehaviorScript""'s ""Script"" instance;
+ else (no)
+ endif
+ else (no)
+ endif
+endwhile
+
+stop
+
+@enduml
diff --git a/img/class-config.puml b/img/class-config.puml
new file mode 100644
index 0000000..72e5e6c
--- /dev/null
+++ b/img/class-config.puml
@@ -0,0 +1,14 @@
+@startuml
+!include theme.ipuml
+
+class Config <<singleton>> {
+ - Config()
+ + ~Config()
+ --
+ + get_instance() : Config & <<static>>
+ --
+ + log : struct ...
+ ...
+}
+
+@enduml
diff --git a/img/class-savemgr.puml b/img/class-savemgr.puml
new file mode 100644
index 0000000..30bcd08
--- /dev/null
+++ b/img/class-savemgr.puml
@@ -0,0 +1,13 @@
+@startuml
+!include theme.ipuml
+skinparam Linetype ortho
+
+class SaveManager {
+
+}
+
+class ValueBroker {
+
+}
+
+@enduml
diff --git a/img/class-scripts.puml b/img/class-scripts.puml
index 8fc36c9..44cbe85 100644
--- a/img/class-scripts.puml
+++ b/img/class-scripts.puml
@@ -10,10 +10,12 @@ package api {
class Component <<irrelevant>>
class Script {
+ - Script()
+ --
# init() <<virtual>>
# update() <<virtual>>
--
- - Script()
+ - parent : BehaviorScript *
}
class BehaviorScript {
@@ -26,7 +28,8 @@ package api {
}
BehaviorScript -u-|> Component
- Script .u.> BehaviorScript
+ Script <.u. BehaviorScript : > friend
+ Script ..u> BehaviorScript
}
class System <<irrelevant>>
diff --git a/img/poc-log.png b/img/poc-log.png
new file mode 100644
index 0000000..12e2c61
--- /dev/null
+++ b/img/poc-log.png
Binary files differ
diff --git a/img/poc-output-scripts.png b/img/poc-output-scripts.png
new file mode 100644
index 0000000..068f345
--- /dev/null
+++ b/img/poc-output-scripts.png
Binary files differ
diff --git a/img/theme.ipuml b/img/theme.ipuml
index 81391e2..c44db05 100644
--- a/img/theme.ipuml
+++ b/img/theme.ipuml
@@ -1,18 +1,10 @@
' vim:ft=plantuml
-
-<style>
-root {
- BackgroundColor white
- FontColor black
- LineThickness 1
- Margin 0
-}
-</style>
-
+!theme plain
skinparam ClassAttributeIconSize 0
skinparam ClassFontStyle bold
skinparam DefaultFontName Inter
skinparam DefaultFontSize 10
+skinparam DefaultMonospacedFontName "JetBrains Mono"
skinparam MaxMessageSize 200
skinparam Nodesep 25
' skinparam Padding 0
@@ -20,6 +12,8 @@ skinparam Ranksep 50
skinparam RoundCorner 0
skinparam PackageStyle rectangle
skinparam PackageFontStyle italic
+skinparam ActivityStartColor black
+skinparam ActivityEndColor black
hide class circle
diff --git a/projdoc.cls b/projdoc.cls
index b369b18..a0c8e10 100644
--- a/projdoc.cls
+++ b/projdoc.cls
@@ -330,7 +330,7 @@
% adjust scale for puml diagrams
\newcommand{\includepumldiag}[1]{%
\StrSubstitute{#1}{.puml}{.eps}[\filename]%
- \fitimg{\includegraphics[scale=0.75]{\filename}}%
+ \fitimg{\includegraphics[scale=0.65]{\filename}}%
}
% prevent page break between two paragraphs
diff --git a/reqs.toml b/reqs.toml
index a83208e..9ad0a86 100644
--- a/reqs.toml
+++ b/reqs.toml
@@ -117,3 +117,68 @@ Unless explicitly changed by the game programmer, methods on instances of
must be called by the script system.
'''
+[savemgr]
+type = 'user'
+priority = 'must'
+description = '''
+The engine provides an \gls{api} for saving various kinds of game data
+(e.g.~progress, levels, statistics, unlocked items, achievements).
+'''
+
+[savemgr:journalling]
+type = 'system'
+priority = 'should'
+description = '''
+The save manager uses a journal to store data, such that partial saves do not
+cause data loss.
+'''
+
+[savemgr:types-custom]
+type = 'system'
+priority = 'will not'
+description = '''
+The save manager can be extended to store and retrieve game programmer-defined
+types and data structures.
+'''
+
+[savemgr:types-scalar]
+type = 'system'
+priority = 'must'
+description = '''
+The save manager is able to store and retrieve scalar types.
+'''
+
+[savemgr:types-string]
+type = 'system'
+priority = 'must'
+description = '''
+The save manager is able to store and retrieve strings.
+'''
+
+[savemgr:multi-file]
+type = 'system'
+priority = 'will not'
+description = '''
+The save manager can load multiple different save files.
+'''
+
+[savemgr:file-manage]
+type = 'system'
+priority = 'must'
+description = '''
+The save manager manages opening/closing the underlying file, and flushing
+in-memory data to the file.
+'''
+done = '''
+The game programmer is able to use the save manager without explicit
+(de)initialization.
+'''
+
+[savemgr:var-key]
+type = 'system'
+priority = 'must'
+description = '''
+The save manager provides access to variables uniquely identified by a key
+string.
+'''
+
diff --git a/time.txt b/time.txt
index 7c8e267..8dde3cb 100644
--- a/time.txt
+++ b/time.txt
@@ -92,6 +92,7 @@ loek: 2024-10-30 30m project meeting (min/max component count constraints)
loek: 2024-10-24 10m review :: PR review (#40, #41 and #42)
loek: 2024-10-31 3h45m project meeting
loek: 2024-10-31 2h50m docs :: design :: POCs
+loek: 2024-11-01 1h05m docs :: design (log POC & Global configuration interface)
max: 2024-09-02 1h project kickoff
max: 2024-09-02 45m first project meeting