197 lines
6.4 KiB
C++

/*
** Command & Conquer Generals(tm)
** Copyright 2025 Electronic Arts Inc.
**
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation, either version 3 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: SparseMatchFinder.h /////////////////////////////////////////////////////////////////////////
// Author: Steven Johnson, March 2002
// Desc:
///////////////////////////////////////////////////////////////////////////////////////////////////
#pragma once
#ifndef __SparseMatchFinder_H_
#define __SparseMatchFinder_H_
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "Common/BitFlags.h"
#include "Common/STLTypedefs.h"
#if defined(_DEBUG) || defined(_INTERNAL)
#define SPARSEMATCH_DEBUG
#else
#undef SPARSEMATCH_DEBUG
#endif
//-------------------------------------------------------------------------------------------------
template<class MATCHABLE, class BITSET>
class SparseMatchFinder
{
private:
//-------------------------------------------------------------------------------------------------
// TYPEDEFS
//-------------------------------------------------------------------------------------------------
struct HashMapHelper
{
size_t operator()(const BITSET& p) const
{
/// @todo srj -- provide a better hash function for BITSET
size_t result = 0;
for (int i = 0; i < p.size(); ++i)
if (p.test(i))
result += (i + 1);
return result;
}
Bool operator()(const BITSET& a, const BITSET& b) const
{
return (a == b);
}
};
//-------------------------------------------------------------------------------------------------
typedef std::hash_map< BITSET, const MATCHABLE*, HashMapHelper, HashMapHelper > MatchMap;
//-------------------------------------------------------------------------------------------------
// MEMBER VARS
//-------------------------------------------------------------------------------------------------
mutable MatchMap m_bestMatches;
//-------------------------------------------------------------------------------------------------
// METHODS
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
inline static Int countConditionIntersection(const BITSET& a, const BITSET& b)
{
return a.countIntersection(b);
}
//-------------------------------------------------------------------------------------------------
inline static Int countConditionInverseIntersection(const BITSET& a, const BITSET& b)
{
return a.countInverseIntersection(b);
}
//-------------------------------------------------------------------------------------------------
const MATCHABLE* findBestInfoSlow(const std::vector<MATCHABLE>& v, const BITSET& bits) const
{
const MATCHABLE* result = NULL;
Int bestYesMatch = 0; // want to maximize this
Int bestYesExtraneousBits = 999; // want to minimize this
#ifdef SPARSEMATCH_DEBUG
Int numDupMatches = 0;
AsciiString curBestMatchStr, dupMatchStr;
#endif
for (std::vector<MATCHABLE>::const_iterator it = v.begin(); it != v.end(); ++it)
{
for (Int i = it->getConditionsYesCount()-1; i >= 0; --i)
{
const BITSET& yesFlags = it->getNthConditionsYes(i);
// the best match has the most "yes" matches and the smallest number of "no" matches.
// if there are ties, then prefer the model with the smaller number of irrelevant 'yes' bits.
// (example of why tiebreaker is necessary: if we want to match FRONTCRUSHED,
// it would tie with both FRONTCRUSHED and FRONTCRUSHED|BACKCRUSHED, since they
// both match a single YES bit.)
Int yesMatch = countConditionIntersection(bits, yesFlags);
Int yesExtraneousBits = countConditionInverseIntersection(bits, yesFlags);
#ifdef SPARSEMATCH_DEBUG
if (yesMatch == bestYesMatch &&
yesExtraneousBits == bestYesExtraneousBits)
{
++numDupMatches;
dupMatchStr = it->getDescription();
}
#endif
if ((yesMatch > bestYesMatch) ||
(yesMatch >= bestYesMatch && yesExtraneousBits < bestYesExtraneousBits))
{
result = &(*it);
bestYesMatch = yesMatch;
bestYesExtraneousBits = yesExtraneousBits;
#ifdef SPARSEMATCH_DEBUG
numDupMatches = 0;
curBestMatchStr = it->getDescription();
#endif
}
} // end for i
} // end for it
#ifdef SPARSEMATCH_DEBUG
if (numDupMatches > 0)
{
AsciiString curConditionStr;
bits.buildDescription(&curConditionStr);
DEBUG_CRASH(("ambiguous model match in findBestInfoSlow \n\nbetween \n(%s)\n<and>\n(%s)\n\n(%d extra matches found)\n\ncurrent bits are (\n%s)\n",
curBestMatchStr.str(),
dupMatchStr.str(),
numDupMatches,
curConditionStr.str()));
}
#endif
return result;
}
//-------------------------------------------------------------------------------------------------
public:
//-------------------------------------------------------------------------------------------------
void clear()
{
m_bestMatches.clear();
}
//-------------------------------------------------------------------------------------------------
const MATCHABLE* findBestInfo(const std::vector<MATCHABLE>& v, const BITSET& bits) const
{
MatchMap::const_iterator it = m_bestMatches.find(bits);
if (it != m_bestMatches.end())
{
return (*it).second;
}
const MATCHABLE* info = findBestInfoSlow(v, bits);
DEBUG_ASSERTCRASH(info != NULL, ("no suitable match for criteria was found!\n"));
if (info != NULL)
m_bestMatches[bits] = info;
return info;
}
};
#endif // __SparseMatchFinder_H_