Skip to content

Commit

Permalink
Lazily initialize the Factory
Browse files Browse the repository at this point in the history
  • Loading branch information
messmerd committed Nov 23, 2022
1 parent a40c23e commit f0862fb
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 93 deletions.
78 changes: 44 additions & 34 deletions include/core/factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,7 @@ namespace detail {
template<class T> constexpr bool reflection_enabled_v = std::is_base_of_v<EnableReflectionBase, T>;
} // namespace detail


template <class Base>
template<class Base>
class BuilderBase
{
public:
Expand All @@ -47,7 +46,7 @@ class BuilderBase
};

// Builds an instance of a factory-enabled class; Can specialize this, but it must inherit from BuilderBase and Factory<Base> must have access to its members.
template <class Derived, class Base>
template<class Derived, class Base>
class Builder : public BuilderBase<Base>
{
// NOTE: Derived must have a default constructor accessible by this class and also must have a public destructor.
Expand All @@ -65,79 +64,90 @@ struct InfoBase
};

// Static data for a factory-enabled class; Can specialize this, but it must inherit from InfoBase.
template <class T>
template<class T>
struct Info : public InfoBase {};


template <class Base>
template<class Base>
class Factory
{
private:

Factory() = delete;
virtual ~Factory() { Clear(); }
~Factory() { Clear(); }

Factory(const Factory&) = delete;
Factory(Factory&&) = delete;
Factory& operator=(const Factory&) = delete;
Factory& operator=(Factory&&) = delete;

// Declaration. Factories will have to implement their own.
static void InitializeImpl();

public:

static void Initialize()
struct InitializeImpl
{
if (m_Initialized)
return;

// Note: Factories will have to implement this.
InitializeImpl();
m_Initialized = true;
};

/*
* Initialize must be called at the start of every public Factory method to
* ensure lazy initialization of the Factory whenever it is first used.
*/
static bool Initialize()
{
if (!m_Initialized)
{
Clear();
// This gets around static initialization ordering issues:
[[maybe_unused]] auto init = std::make_unique<InitializeImpl>();
m_Initialized = true;
}
return true;
}

public:

static std::shared_ptr<Base> Create(TypeEnum classType)
{
if (!m_Initialized)
throw std::runtime_error("Factory is not initialized for base class: " + std::string(typeid(Base).name()));
[[maybe_unused]] static bool init = Initialize();
if (m_Builders.find(classType) != m_Builders.end())
return m_Builders.at(classType)->Build();
throw std::runtime_error("Factory is not initialized for TypeEnum '" + std::to_string(static_cast<int>(classType)) + ".");
assert(false && "Factory is not initialized for Base.");
return nullptr;
}

static Info<Base> const* GetInfo(TypeEnum classType)
{
if (!m_Initialized)
throw std::runtime_error("Factory is not initialized for base class: " + std::string(typeid(Base).name()));
[[maybe_unused]] static bool init = Initialize();
if (m_Info.find(classType) != m_Info.end())
return m_Info.at(classType).get();
throw std::runtime_error("Factory is not initialized for TypeEnum '" + std::to_string(static_cast<int>(classType)) + ".");
assert(false && "Factory is not initialized for Base.");
return nullptr;
}

template <class Derived, class = std::enable_if_t<detail::factory_enabled_v<Derived>>>
template<class Derived, std::enable_if_t<detail::factory_enabled_v<Derived>, bool> = true>
static std::shared_ptr<Derived> Create()
{
if (!m_Initialized)
throw std::runtime_error("Factory is not initialized for base class: " + std::string(typeid(Base).name()));
// Initialize() not needed here because GetEnumFromType calls it
static TypeEnum classType = GetEnumFromType<Derived>();
return std::static_pointer_cast<Derived>(Create(classType));
}

template <class Derived, class = std::enable_if_t<detail::factory_enabled_v<Derived>>>
template<class Derived, std::enable_if_t<detail::factory_enabled_v<Derived>, bool> = true>
static Info<Base> const* GetInfo()
{
if (!m_Initialized)
throw std::runtime_error("Factory is not initialized for base class: " + std::string(typeid(Base).name()));
// Initialize() not needed here because GetEnumFromType calls it
static TypeEnum classType = GetEnumFromType<Derived>();
return GetInfo(classType);
}

static const std::map<TypeEnum, std::unique_ptr<const Info<Base>>>& TypeInfo() { return m_Info; }
static const std::map<TypeEnum, std::unique_ptr<const Info<Base>>>& TypeInfo()
{
[[maybe_unused]] static bool init = Initialize();
return m_Info;
}

static std::vector<TypeEnum> GetInitializedTypes()
{
[[maybe_unused]] static bool init = Initialize();
std::vector<ModuleType> vec;
for (const auto& mapPair : m_Builders)
{
Expand All @@ -146,15 +156,15 @@ class Factory
return vec;
}

template<class Type, class = std::enable_if_t<detail::factory_enabled_v<Type>>>
template<class Type, std::enable_if_t<detail::factory_enabled_v<Type>, bool> = true>
static TypeEnum GetEnumFromType()
{
if (!m_Initialized)
throw std::runtime_error("Factory is not initialized for base class: " + std::string(typeid(Base).name()));
[[maybe_unused]] static bool init = Initialize();
const auto& type = std::type_index(typeid(Type));
if (m_TypeToEnum.find(type) != m_TypeToEnum.end())
return m_TypeToEnum.at(type);
throw std::runtime_error("Factory is not initialized for Type '" + std::string(typeid(Type).name()) + ".");
assert(false && "Factory is not initialized for Type.");
return TypeInvalid;
}

private:
Expand Down Expand Up @@ -248,7 +258,7 @@ struct ReflectionImpl : public Base


// Inherit this class using CRTP to enable factory for any class
template <class Derived, class Base>
template<class Derived, class Base>
struct EnableFactory : public detail::EnableFactoryBase, public std::conditional_t<detail::reflection_enabled_v<Base>, ReflectionImpl<Derived, Base>, Base> // See note above
{
static_assert(std::is_base_of_v<InfoBase, Info<Derived>>, "Info<Derived> must inherit from InfoBase");
Expand Down
16 changes: 8 additions & 8 deletions include/core/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class OptionDefinition
OptionDefinition() : m_OptionType(OPTION), m_Id(-1), m_ValueType(Type::BOOL), m_Name(""), m_ShortName('\0'), m_DefaultValue(false) {}

// OptionDefinition without accepted values; The value can be anything allowed by the variant
template<typename T, typename = std::enable_if_t<std::is_integral<T>{} || (std::is_enum_v<T> && std::is_convertible_v<std::underlying_type_t<T>, int>)>>
template<typename T, std::enable_if_t<std::is_integral<T>{} || (std::is_enum_v<T> && std::is_convertible_v<std::underlying_type_t<T>, int>), bool> = true>
OptionDefinition(OptionType type, T id, const std::string& name, char shortName, const value_t& defaultValue, const std::string& description)
: m_OptionType(type), m_Id(static_cast<int>(id)), m_Name(name), m_ShortName(shortName), m_DefaultValue(defaultValue), m_AcceptedValues({}), m_AcceptedValuesOrdered({}), m_Description(description)
{
Expand All @@ -69,8 +69,8 @@ class OptionDefinition

// OptionDefinition with accepted values; Ensures that defaultValue and acceptedValues are the same type and are a valid variant alternative
template <typename T, typename U,
typename = std::enable_if_t<std::is_constructible_v<value_t, U> && /* U must be a valid variant alternative */
(std::is_integral_v<T> || (std::is_enum_v<T> && std::is_convertible_v<std::underlying_type_t<T>, int>))>> /* T must be int or enum class with int underlying type */
std::enable_if_t<std::is_constructible_v<value_t, U> && /* U must be a valid variant alternative */
(std::is_integral_v<T> || (std::is_enum_v<T> && std::is_convertible_v<std::underlying_type_t<T>, int>)), bool> = true> /* T must be int or enum class with int underlying type */
OptionDefinition(OptionType type, T id, const std::string& name, char shortName, const U& defaultValue, const std::initializer_list<U>& acceptedValues, const std::string& description)
: m_OptionType(type), m_Id(static_cast<int>(id)), m_Name(name), m_ShortName(shortName), m_DefaultValue(defaultValue), m_Description(description)
{
Expand Down Expand Up @@ -110,13 +110,13 @@ class OptionDefinition
}

// Allows the use of string literals, which are converted to std::string
template<typename T, typename = std::enable_if_t<std::is_integral_v<T> || (std::is_enum_v<T> && std::is_convertible_v<std::underlying_type_t<T>, int>)>>
template<typename T, std::enable_if_t<std::is_integral_v<T> || (std::is_enum_v<T> && std::is_convertible_v<std::underlying_type_t<T>, int>), bool> = true>
OptionDefinition(OptionType type, T id, const std::string& name, char shortName, const char* defaultValue, const std::initializer_list<std::string>& acceptedValues, const std::string& description)
: OptionDefinition(type, id, name, shortName, std::string(defaultValue), acceptedValues, description) {}

// Allows custom accepted values text which is used when printing help for this option. m_AcceptedValues is empty.
template<typename T, typename U, typename = std::enable_if_t<std::is_constructible_v<value_t, U> && /* U must be a valid variant alternative */
(std::is_integral_v<T> || (std::is_enum_v<T> && std::is_convertible_v<std::underlying_type_t<T>, int>))>> /* T must be int or enum class with int underlying type */
template<typename T, typename U, std::enable_if_t<std::is_constructible_v<value_t, U> && /* U must be a valid variant alternative */
(std::is_integral_v<T> || (std::is_enum_v<T> && std::is_convertible_v<std::underlying_type_t<T>, int>)), bool> = true> /* T must be int or enum class with int underlying type */
OptionDefinition(OptionType type, T id, const std::string& name, char shortName, const U& defaultValue, const char* customAcceptedValuesText, const std::string& description)
: OptionDefinition(type, id, name, shortName, defaultValue, description)
{
Expand Down Expand Up @@ -286,13 +286,13 @@ class OptionCollection
const Option& GetOption(int id) const { return m_OptionsMap.at(id); }
Option& GetOption(int id) { return m_OptionsMap[id]; }

template<typename T, class = std::enable_if_t<std::is_enum_v<T> && std::is_convertible_v<std::underlying_type_t<T>, int>>>
template<typename T, std::enable_if_t<std::is_enum_v<T> && std::is_convertible_v<std::underlying_type_t<T>, int>, bool> = true>
const Option& GetOption(T id) const
{
return GetOption(static_cast<int>(id));
}

template<typename T, class = std::enable_if_t<std::is_enum_v<T> && std::is_convertible_v<std::underlying_type_t<T>, int>>>
template<typename T, std::enable_if_t<std::is_enum_v<T> && std::is_convertible_v<std::underlying_type_t<T>, int>, bool> = true>
Option& GetOption(T id)
{
return GetOption(static_cast<int>(id));
Expand Down
2 changes: 1 addition & 1 deletion include/core/status.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class ModuleException : public std::exception
ModuleException& operator=(ModuleException& other) = default;

// Construct using an enum for an error code
template <class T, class = std::enable_if_t<std::is_enum_v<T> && std::is_convertible_v<std::underlying_type_t<T>, int>>>
template <class T, std::enable_if_t<std::is_enum_v<T> && std::is_convertible_v<std::underlying_type_t<T>, int>, bool> = true>
ModuleException(Category category, T errorCode, const std::string& errorMessage = "")
: ModuleException(category, static_cast<int>(errorCode), errorMessage) {}

Expand Down
7 changes: 0 additions & 7 deletions include/dmf2mod.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,3 @@

#include "dmf.h"
#include "mod.h"

namespace d2m {

// Call this before using the dmf2mod library
void Initialize();

} // namespace d2m
2 changes: 1 addition & 1 deletion include/utils/stream_reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ enum class Endianness { Unspecified, Little, Big };
Wrapper for std::istream and derived classes which provides
convenient methods for reading strings and integers
*/
template <class IStream, Endianness GlobalEndian = Endianness::Unspecified, class = std::enable_if_t<std::is_base_of_v<std::basic_istream<char>, IStream>>>
template <class IStream, Endianness GlobalEndian = Endianness::Unspecified, std::enable_if_t<std::is_base_of_v<std::basic_istream<char>, IStream>, bool> = true>
class StreamReader
{
private:
Expand Down
2 changes: 0 additions & 2 deletions src/console/console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ static void PrintHelp(const std::string& executable, ModuleType moduleType);

int main(int argc, char *argv[])
{
Initialize();

auto args = Utils::GetArgsAsVector(argc, argv);

InputOutput io;
Expand Down
29 changes: 28 additions & 1 deletion src/core/factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,36 @@
factory.cpp
Written by Dalton Messmer <[email protected]>.
See factory.h
Implements InitializeImpl for each factory.
*/

#include "factory.h"
#include "dmf2mod.h"

using namespace d2m;

using MODOptionEnum = MODConversionOptions::OptionEnum;
static auto MODOptions = OptionDefinitionCollection
{
/* Type / Option id / Full name / Short / Default / Possib. vals / Description */
{OPTION, MODOptionEnum::AmigaFilter, "amiga", '\0', false, "Enables the Amiga filter"},
{OPTION, MODOptionEnum::Arpeggio, "arp", '\0', false, "Allow arpeggio effects"},
{OPTION, MODOptionEnum::Portamento, "port", '\0', false, "Allow portamento up/down effects"},
{OPTION, MODOptionEnum::Port2Note, "port2note", '\0', false, "Allow portamento to note effects"},
{OPTION, MODOptionEnum::Vibrato, "vib", '\0', false, "Allow vibrato effects"},
{OPTION, MODOptionEnum::TempoType, "tempo", '\0', "accuracy", {"accuracy", "compat"}, "Prioritize tempo accuracy or compatibility with effects"},
};

template<>
Factory<ConversionOptions>::InitializeImpl::InitializeImpl()
{
Register<ModuleType::DMF, DMFConversionOptions>();
Register<ModuleType::MOD, MODConversionOptions>(std::move(MODOptions));
};

template<>
Factory<Module>::InitializeImpl::InitializeImpl()
{
Register<ModuleType::DMF, DMF>("Deflemask", "dmf");
Register<ModuleType::MOD, MOD>("ProTracker", "mod");
};
38 changes: 1 addition & 37 deletions src/dmf2mod.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,9 @@
dmf2mod.cpp
Written by Dalton Messmer <[email protected]>.
Implements initialize methods for each factory.
See dmf2mod.h
*/

#include "dmf2mod.h"

using namespace d2m;

using MODOptionEnum = MODConversionOptions::OptionEnum;
static auto MODOptions = OptionDefinitionCollection
{
/* Type / Option id / Full name / Short / Default / Possib. vals / Description */
{OPTION, MODOptionEnum::AmigaFilter, "amiga", '\0', false, "Enables the Amiga filter"},
{OPTION, MODOptionEnum::Arpeggio, "arp", '\0', false, "Allow arpeggio effects"},
{OPTION, MODOptionEnum::Portamento, "port", '\0', false, "Allow portamento up/down effects"},
{OPTION, MODOptionEnum::Port2Note, "port2note", '\0', false, "Allow portamento to note effects"},
{OPTION, MODOptionEnum::Vibrato, "vib", '\0', false, "Allow vibrato effects"},
{OPTION, MODOptionEnum::TempoType, "tempo", '\0', "accuracy", {"accuracy", "compat"}, "Prioritize tempo accuracy or compatibility with effects"},
};

template<>
void Factory<ConversionOptions>::InitializeImpl()
{
Clear();

Register<ModuleType::DMF, DMFConversionOptions>();
Register<ModuleType::MOD, MODConversionOptions>(std::move(MODOptions));
}

template<>
void Factory<Module>::InitializeImpl()
{
Clear();

Register<ModuleType::DMF, DMF>("Deflemask", "dmf");
Register<ModuleType::MOD, MOD>("ProTracker", "mod");
}

void d2m::Initialize()
{
Factory<Module>::Initialize();
Factory<ConversionOptions>::Initialize();
}
2 changes: 0 additions & 2 deletions src/webapp/webapp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ static void SetStatusType(bool isError);

int main()
{
Initialize();

// Initialize global options (for web app, user won't provide them)
GlobalOptions::Get().GetOption(GlobalOptions::OptionEnum::Force).SetValue(true);
GlobalOptions::Get().GetOption(GlobalOptions::OptionEnum::Verbose).SetValue(false);
Expand Down

0 comments on commit f0862fb

Please sign in to comment.