diff --git a/src/Common/StateMap/StateMapLayout.cpp b/src/Common/StateMap/StateMapLayout.cpp new file mode 100644 index 00000000..37fc8a65 --- /dev/null +++ b/src/Common/StateMap/StateMapLayout.cpp @@ -0,0 +1,14 @@ +#include "StateMapLayout.h" + +using namespace state_map; + +StateMapLayoutEntry::StateMapLayoutEntry(std::string name, const size_t resultCount) + : m_name(std::move(name)), + m_result_count(resultCount) +{ +} + +StateMapLayout::StateMapLayout(std::vector layoutEntries) + : m_layout_entries(std::move(layoutEntries)) +{ +} diff --git a/src/Common/StateMap/StateMapLayout.h b/src/Common/StateMap/StateMapLayout.h new file mode 100644 index 00000000..19bee4d7 --- /dev/null +++ b/src/Common/StateMap/StateMapLayout.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +namespace state_map +{ + class StateMapLayoutEntry + { + public: + StateMapLayoutEntry(std::string name, size_t resultCount); + + std::string m_name; + size_t m_result_count; + }; + + class StateMapLayout + { + public: + explicit StateMapLayout(std::vector layoutEntries); + + std::vector m_layout_entries; + }; +} diff --git a/src/ObjCommon/Game/IW4/TechsetConstantsIW4.h b/src/ObjCommon/Game/IW4/TechsetConstantsIW4.h index e3f0bc17..fce75257 100644 --- a/src/ObjCommon/Game/IW4/TechsetConstantsIW4.h +++ b/src/ObjCommon/Game/IW4/TechsetConstantsIW4.h @@ -5,6 +5,7 @@ #include "Game/IW4/CommonIW4.h" #include "Game/IW4/IW4.h" +#include "StateMap/StateMapLayout.h" namespace IW4 { @@ -560,4 +561,33 @@ namespace IW4 MakeKnownConstantName("falloffBeginColor"), MakeKnownConstantName("falloffEndColor"), }; + + enum class StateMapLayout_e + { + ALPHA_TEST, + BLEND_FUNC, + SEPARATE_ALPHA_BLEND_FUNC, + CULL_FACE, + DEPTH_TEST, + DEPTH_WRITE, + COLOR_WRITE, + GAMMA_WRITE, + POLYGON_OFFSET, + STENCIL, + WIREFRAME + }; + + inline state_map::StateMapLayout stateMapLayout({ + {"alphaTest", 1u}, + {"blendFunc", 3u}, + {"separateAlphaBlendFunc", 3u}, + {"cullFace", 1u}, + {"depthTest", 1u}, + {"depthWrite", 1u}, + {"colorWrite", 2u}, + {"gammaWrite", 1u}, + {"polygonOffset", 1u}, + {"stencil", 9u}, + {"wireframe", 1u}, + }); } diff --git a/src/ObjLoading/StateMap/Parsing/Matcher/StateMapExpressionMatchers.cpp b/src/ObjLoading/StateMap/Parsing/Matcher/StateMapExpressionMatchers.cpp new file mode 100644 index 00000000..3e38a65a --- /dev/null +++ b/src/ObjLoading/StateMap/Parsing/Matcher/StateMapExpressionMatchers.cpp @@ -0,0 +1,33 @@ +#include "StateMapExpressionMatchers.h" + +#include "Parsing/Simple/Expression/SimpleExpressionScopeValue.h" +#include "Parsing/Simple/Matcher/SimpleMatcherFactory.h" + +using namespace state_map; + +static constexpr int CAPTURE_IDENTIFIER = SimpleExpressionMatchers::CAPTURE_OFFSET_EXPRESSION_EXT + 1; + +StateMapExpressionMatchers::StateMapExpressionMatchers() + : SimpleExpressionMatchers(false, false, false, false, true) +{ +} + +std::unique_ptr StateMapExpressionMatchers::ParseOperandExtension(const supplier_t* labelSupplier) const +{ + const SimpleMatcherFactory create(labelSupplier); + + return create.Or({ + create.Identifier().Capture(CAPTURE_IDENTIFIER) + }); +} + +std::unique_ptr StateMapExpressionMatchers::ProcessOperandExtension(SequenceResult& result) const +{ + const auto& identifierToken = result.NextCapture(CAPTURE_IDENTIFIER); + auto identifierValue = identifierToken.IdentifierValue(); + + if (identifierValue.rfind("mtl", 0) == 0) + return std::make_unique(identifierValue); + + return std::make_unique(identifierValue); +} diff --git a/src/ObjLoading/StateMap/Parsing/Matcher/StateMapExpressionMatchers.h b/src/ObjLoading/StateMap/Parsing/Matcher/StateMapExpressionMatchers.h new file mode 100644 index 00000000..b2d5215e --- /dev/null +++ b/src/ObjLoading/StateMap/Parsing/Matcher/StateMapExpressionMatchers.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +#include "Parsing/Simple/Expression/SimpleExpressionMatchers.h" + +namespace state_map +{ + class StateMapExpressionMatchers final : public SimpleExpressionMatchers + { + public: + StateMapExpressionMatchers(); + + protected: + std::unique_ptr ParseOperandExtension(const supplier_t* labelSupplier) const override; + std::unique_ptr ProcessOperandExtension(SequenceResult& result) const override; + }; +} diff --git a/src/ObjLoading/StateMap/Parsing/StateMapParser.cpp b/src/ObjLoading/StateMap/Parsing/StateMapParser.cpp new file mode 100644 index 00000000..8b102131 --- /dev/null +++ b/src/ObjLoading/StateMap/Parsing/StateMapParser.cpp @@ -0,0 +1,184 @@ +#include "StateMapParser.h" + +#include "Matcher/StateMapExpressionMatchers.h" +#include "Parsing/Simple/Matcher/SimpleMatcherFactory.h" + +using namespace state_map; + +namespace state_map +{ + class SequenceStateMapEntry final : public StateMapParser::sequence_t + { + static constexpr auto CAPTURE_ENTRY_NAME = 1; + + public: + SequenceStateMapEntry() + { + const SimpleMatcherFactory create(this); + + AddMatchers({ + create.Identifier().Capture(CAPTURE_ENTRY_NAME), + create.Char('{') + }); + } + + protected: + void ProcessMatch(StateMapParserState* state, SequenceResult& result) const override + { + const auto& entryNameToken = result.NextCapture(CAPTURE_ENTRY_NAME); + + const auto foundEntry = state->m_valid_state_map_entry_names.find(entryNameToken.IdentifierValue()); + if (foundEntry == state->m_valid_state_map_entry_names.end()) + throw ParsingException(entryNameToken.GetPos(), "Unknown entry name"); + + state->m_in_entry = true; + state->m_current_entry_index = foundEntry->second; + } + }; + + class SequenceStateMapEntryClose final : public StateMapParser::sequence_t + { + public: + SequenceStateMapEntryClose() + { + const SimpleMatcherFactory create(this); + + AddMatchers({ + create.Char('}') + }); + } + + protected: + void ProcessMatch(StateMapParserState* state, SequenceResult& result) const override + { + state->m_in_entry = false; + } + }; + + class SequenceCondition final : public StateMapParser::sequence_t + { + StateMapExpressionMatchers m_expression_matchers; + + public: + SequenceCondition() + { + AddLabeledMatchers(m_expression_matchers.Expression(this), StateMapExpressionMatchers::LABEL_EXPRESSION); + const SimpleMatcherFactory create(this); + + AddMatchers({ + create.Label(StateMapExpressionMatchers::LABEL_EXPRESSION), + create.Char(':') + }); + } + + protected: + void ProcessMatch(StateMapParserState* state, SequenceResult& result) const override + { + assert(state->m_in_entry); + + auto expression = m_expression_matchers.ProcessExpression(result); + + if (!state->m_current_rule) + { + auto newRule = std::make_unique(); + state->m_current_rule = newRule.get(); + state->m_definition->m_state_map_entries[state->m_current_entry_index].m_rules.emplace_back(std::move(newRule)); + } + + state->m_current_rule->m_conditions.emplace_back(std::move(expression)); + } + }; + + class SequenceValue final : public StateMapParser::sequence_t + { + static constexpr auto TAG_PASSTHROUGH = 1; + static constexpr auto TAG_VALUE_LIST = 2; + + static constexpr auto CAPTURE_VALUE = 1; + + static constexpr auto LABEL_VALUE_LIST = 1; + + public: + SequenceValue() + { + const SimpleMatcherFactory create(this); + + AddLabeledMatchers({ + create.Identifier().Capture(CAPTURE_VALUE), + create.OptionalLoop(create.And({ + create.Char(','), + create.Identifier().Capture(CAPTURE_VALUE) + })) + }, LABEL_VALUE_LIST); + + AddMatchers({ + create.Or({ + create.Keyword("passthrough").Tag(TAG_PASSTHROUGH), + create.Label(LABEL_VALUE_LIST).Tag(TAG_VALUE_LIST) + }), + create.Char(';') + }); + } + + protected: + void ProcessMatch(StateMapParserState* state, SequenceResult& result) const override + { + assert(state->m_in_entry); + assert(state->m_current_rule); + + if (result.PeekAndRemoveIfTag(TAG_VALUE_LIST) == TAG_VALUE_LIST) + { + while (result.HasNextCapture(CAPTURE_VALUE)) + state->m_current_rule->m_values.emplace_back(result.NextCapture(CAPTURE_VALUE).IdentifierValue()); + } + else + { + // A rule without values is considered passthrough therefore just don't add values + assert(result.PeekAndRemoveIfTag(TAG_PASSTHROUGH) == TAG_PASSTHROUGH); + } + + state->m_current_rule = nullptr; + } + }; +} + +StateMapParser::StateMapParser(SimpleLexer* lexer, const StateMapLayout& layout) + : AbstractParser(lexer, std::make_unique(layout)) +{ +} + +const std::vector& StateMapParser::GetTestsForState() +{ + static std::vector rootSequences({ + new SequenceStateMapEntry() + }); + + static std::vector entrySequences({ + new SequenceStateMapEntryClose(), + new SequenceCondition() + }); + + static std::vector ruleSequences({ + new SequenceCondition(), + new SequenceValue() + }); + + if (m_state->m_in_entry) + { + return m_state->m_current_rule + ? ruleSequences + : entrySequences; + } + + return rootSequences; +} + +std::unique_ptr StateMapParser::GetStateMapDefinition() const +{ + return std::move(m_state->m_definition); +} + +StateMapParserState* StateMapParser::GetState() const +{ + return m_state.get(); +} diff --git a/src/ObjLoading/StateMap/Parsing/StateMapParser.h b/src/ObjLoading/StateMap/Parsing/StateMapParser.h new file mode 100644 index 00000000..2f88fc58 --- /dev/null +++ b/src/ObjLoading/StateMap/Parsing/StateMapParser.h @@ -0,0 +1,22 @@ +#pragma once + +#include "StateMapParserState.h" +#include "Utils/ClassUtils.h" +#include "Techset/TechsetDefinition.h" +#include "Parsing/Simple/SimpleLexer.h" +#include "Parsing/Simple/SimpleParserValue.h" +#include "Parsing/Impl/AbstractParser.h" + +namespace state_map +{ + class StateMapParser final : public AbstractParser + { + protected: + const std::vector& GetTestsForState() override; + + public: + StateMapParser(SimpleLexer* lexer, const StateMapLayout& layout); + _NODISCARD std::unique_ptr GetStateMapDefinition() const; + _NODISCARD StateMapParserState* GetState() const; + }; +} diff --git a/src/ObjLoading/StateMap/Parsing/StateMapParserState.cpp b/src/ObjLoading/StateMap/Parsing/StateMapParserState.cpp new file mode 100644 index 00000000..b6ad9cf4 --- /dev/null +++ b/src/ObjLoading/StateMap/Parsing/StateMapParserState.cpp @@ -0,0 +1,13 @@ +#include "StateMapParserState.h" + +using namespace state_map; + +StateMapParserState::StateMapParserState(const StateMapLayout& layout) + : m_layout(layout), + m_definition(std::make_unique(layout.m_layout_entries.size())), + m_in_entry(false), + m_current_entry_index(0u) +{ + for (auto i = 0u; i < m_layout.m_layout_entries.size(); i++) + m_valid_state_map_entry_names.emplace(m_layout.m_layout_entries[i].m_name, i); +} diff --git a/src/ObjLoading/StateMap/Parsing/StateMapParserState.h b/src/ObjLoading/StateMap/Parsing/StateMapParserState.h new file mode 100644 index 00000000..1c0aa50d --- /dev/null +++ b/src/ObjLoading/StateMap/Parsing/StateMapParserState.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +#include "StateMap/StateMapDefinition.h" +#include "StateMap/StateMapLayout.h" + +namespace state_map +{ + class StateMapParserState + { + public: + const StateMapLayout& m_layout; + std::map m_valid_state_map_entry_names; + std::unique_ptr m_definition; + + bool m_in_entry; + size_t m_current_entry_index; + StateMapRule* m_current_rule; + + explicit StateMapParserState(const StateMapLayout& layout); + }; +} diff --git a/src/ObjLoading/StateMap/StateMapDefinition.cpp b/src/ObjLoading/StateMap/StateMapDefinition.cpp new file mode 100644 index 00000000..c5f77376 --- /dev/null +++ b/src/ObjLoading/StateMap/StateMapDefinition.cpp @@ -0,0 +1,13 @@ +#include "StateMapDefinition.h" + +using namespace state_map; + +bool StateMapRule::IsPassthrough() const +{ + return m_values.empty(); +} + +StateMapDefinition::StateMapDefinition(const size_t entryCount) + : m_state_map_entries(entryCount) +{ +} diff --git a/src/ObjLoading/StateMap/StateMapDefinition.h b/src/ObjLoading/StateMap/StateMapDefinition.h new file mode 100644 index 00000000..2c22ad2b --- /dev/null +++ b/src/ObjLoading/StateMap/StateMapDefinition.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include + +#include "Utils/ClassUtils.h" +#include "Parsing/Simple/Expression/ISimpleExpression.h" + +namespace state_map +{ + class StateMapRule + { + public: + std::vector> m_conditions; + std::vector m_values; + + _NODISCARD bool IsPassthrough() const; + }; + + class StateMapEntry + { + public: + std::vector> m_rules; + }; + + class StateMapDefinition + { + public: + explicit StateMapDefinition(size_t entryCount); + + std::vector m_state_map_entries; + }; +} diff --git a/src/ObjLoading/StateMap/StateMapReader.cpp b/src/ObjLoading/StateMap/StateMapReader.cpp new file mode 100644 index 00000000..e75c38e0 --- /dev/null +++ b/src/ObjLoading/StateMap/StateMapReader.cpp @@ -0,0 +1,59 @@ +#include "StateMapReader.h" + +#include + +#include "Parsing/StateMapParser.h" +#include "Parsing/Impl/CommentRemovingStreamProxy.h" +#include "Parsing/Impl/ParserSingleInputStream.h" + +using namespace state_map; + +StateMapReader::StateMapReader(std::istream& stream, std::string fileName, const StateMapLayout& layout) + : m_file_name(std::move(fileName)), + m_state_map_layout(layout) +{ + m_base_stream = std::make_unique(stream, m_file_name); + m_comment_proxy = std::make_unique(m_base_stream.get()); +} + +bool StateMapReader::IsValidEndState(const StateMapParserState* state) const +{ + if (state->m_current_rule) + { + std::cerr << "In \"" << m_file_name << "\": Unclosed rule at end of file!\n"; + return false; + } + + if (state->m_in_entry) + { + std::cerr << "In \"" << m_file_name << "\": Unclosed entry at end of file!\n"; + return false; + } + + return true; +} + +std::unique_ptr StateMapReader::ReadStateMapDefinition() const +{ + SimpleLexer::Config lexerConfig; + lexerConfig.m_emit_new_line_tokens = false; + lexerConfig.m_read_strings = false; + lexerConfig.m_read_integer_numbers = true; + lexerConfig.m_read_floating_point_numbers = false; + const auto lexer = std::make_unique(m_comment_proxy.get(), std::move(lexerConfig)); + + const auto parser = std::make_unique(lexer.get(), m_state_map_layout); + + const auto success = parser->Parse(); + if (!success) + { + std::cout << "Parsing state map file \"" << m_file_name << "\" failed!\n"; + return {}; + } + + const auto* parserEndState = parser->GetState(); + if (!IsValidEndState(parserEndState)) + return nullptr; + + return parser->GetStateMapDefinition(); +} diff --git a/src/ObjLoading/StateMap/StateMapReader.h b/src/ObjLoading/StateMap/StateMapReader.h new file mode 100644 index 00000000..2338097f --- /dev/null +++ b/src/ObjLoading/StateMap/StateMapReader.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +#include "Utils/ClassUtils.h" +#include "StateMapDefinition.h" +#include "StateMap/StateMapLayout.h" +#include "Parsing/IParserLineStream.h" +#include "Parsing/StateMapParserState.h" + +namespace state_map +{ + class StateMapReader + { + std::string m_file_name; + const StateMapLayout& m_state_map_layout; + std::unique_ptr m_base_stream; + std::unique_ptr m_comment_proxy; + + public: + StateMapReader(std::istream& stream, std::string fileName, const StateMapLayout& layout); + + _NODISCARD bool IsValidEndState(const StateMapParserState* state) const; + _NODISCARD std::unique_ptr ReadStateMapDefinition() const; + }; +}