1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

* Client is able to await for answers for multiple queries at the same time

* Hackish solution allowing AI undertaking actions from event-handling thread
* Fixed crash when death stare or acid breath activated on stack that was just killed
* minor fixes
This commit is contained in:
Michał W. Urbańczyk 2012-03-25 22:46:14 +00:00
parent a3f36ccc71
commit 13f26fc3cb
12 changed files with 176 additions and 65 deletions

View File

@ -793,7 +793,7 @@ void VCAI::heroGotLevel(const CGHeroInstance *hero, int pskill, std::vector<ui16
NET_EVENT_HANDLER;
LOG_ENTRY;
status.addQuery();
callback(0);
requestActionASAP(boost::bind(callback, 0));
}
void VCAI::showBlockingDialog(const std::string &text, const std::vector<Component> &components, ui32 askID, const int soundID, bool selection, bool cancel)
@ -809,7 +809,10 @@ void VCAI::showBlockingDialog(const std::string &text, const std::vector<Compone
if(!selection && cancel) //yes&no -> always answer yes, we are a brave AI :)
sel = 1;
cb->selectionMade(sel, askID);
requestActionASAP([&]()
{
cb->selectionMade(sel, askID);
});
}
void VCAI::showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *down, bool removableUnits, boost::function<void()> &onEnd)
@ -819,8 +822,11 @@ void VCAI::showGarrisonDialog(const CArmedInstance *up, const CGHeroInstance *do
status.addQuery();
//you can't request action from action-response thread
//pickBestCreatures (down, up);
onEnd();
requestActionASAP([=,this]()
{
pickBestCreatures (down, up);
onEnd();
});
}
void VCAI::serialize(COSer<CSaveFile> &h, const int version)
@ -1352,6 +1358,7 @@ bool VCAI::moveHeroToTile(int3 dst, const CGHeroInstance * h)
if(h->tempOwner != playerID) //we lost hero
{
remove_if_present(lockedHeroes, h);
throw std::runtime_error("Hero was lost!"); //we need to throw, otherwise hero will be assigned to sth again
break;
}
@ -1653,7 +1660,15 @@ void VCAI::performTypicalActions()
INDENT;
makePossibleUpgrades(h);
cb->setSelection(h);
wander(h);
try
{
wander(h);
}
catch(std::exception &e)
{
BNLOG("Cannot use this hero anymore, received exception: %s", e.what());
continue;
}
}
}
@ -1787,6 +1802,20 @@ void VCAI::finish()
makingTurn->interrupt();
}
void VCAI::requestActionASAP(boost::function<void()> whatToDo)
{
boost::barrier b(2);
boost::thread newThread([&b,this,whatToDo]()
{
setThreadName(-1, "VCAI::requestActionASAP::helper");
SET_GLOBAL_STATE(this);
boost::shared_lock<boost::shared_mutex> gsLock(cb->getGsMutex());
b.wait();
whatToDo();
});
b.wait();
}
AIStatus::AIStatus()
{
battle = NO_BATTLE;

View File

@ -284,6 +284,9 @@ public:
const CGHeroInstance *primaryHero() const;
TResources estimateIncome() const;
bool containsSavedRes(const TResources &cost) const;
//special function that can be called ONLY from game events handling thread and will send request ASAP
void requestActionASAP(boost::function<void()> whatToDo);
};

View File

@ -59,7 +59,7 @@ void CCallback::selectionMade(int selection, int asker)
{
QueryReply pack(asker,selection);
pack.player = player;
cl->serv->sendPackToServer(pack, player);
sendRequest(&pack);
}
void CCallback::recruitCreatures(const CGObjectInstance *obj, ui32 ID, ui32 amount, si32 level/*=-1*/)
{
@ -222,21 +222,15 @@ int CBattleCallback::battleMakeAction(BattleAction* action)
void CBattleCallback::sendRequest(const CPack* request)
{
//TODO should be part of CClient (client owns connection, not CB)
//but it would have to be very tricky cause template/serialization issues
if(waitTillRealize)
cl->waitingRequest.set(typeList.getTypeID(request));
cl->serv->sendPackToServer(*request, player);
int requestID = cl->sendRequest(request, player);
if(waitTillRealize)
{
tlog5 << boost::format("We'll wait till request %d is answered.\n") % requestID;
unique_ptr<vstd::unlock_shared_guard> unlocker; //optional, if flag set
if(unlockGsWhenWaiting)
unlocker = make_unique<vstd::unlock_shared_guard>(vstd::makeUnlockSharedGuard(getGsMutex()));
unlocker = make_unique<vstd::unlock_shared_guard>(getGsMutex());
cl->waitingRequest.waitWhileTrue();
cl->waitingRequest.waitWhileContains(requestID);
}
}

View File

@ -96,13 +96,11 @@ void CClient::init()
}
CClient::CClient(void)
:waitingRequest(0)
{
init();
}
CClient::CClient(CConnection *con, StartInfo *si)
:waitingRequest(0)
{
init();
newGame(con,si);
@ -121,7 +119,7 @@ void CClient::waitForMoveAndSend(int color)
assert(vstd::contains(battleints, color));
BattleAction ba = battleints[color]->activeStack(gs->curB->getStack(gs->curB->activeStack, false));
MakeAction temp_action(ba);
serv->sendPackToServer(temp_action, color);
sendRequest(&temp_action, color);
return;
}
catch(boost::thread_interrupted&)
@ -175,7 +173,7 @@ void CClient::save(const std::string & fname)
}
SaveGame save_game(fname);
serv->sendPackToServer((CPackForClient&)save_game, getCurrentPlayer());
sendRequest((CPackForClient*)&save_game, 255);
}
void CClient::endGame( bool closeConnection /*= true*/ )
@ -530,7 +528,7 @@ void CClient::stopConnection()
tlog0 << "Connection has been requested to be closed.\n";
boost::unique_lock<boost::mutex>(*serv->wmx);
CloseServer close_server;
serv->sendPackToServer(close_server, 255);
sendRequest(&close_server, 255);
tlog0 << "Sent closing signal to the server\n";
}
@ -599,7 +597,7 @@ void CClient::commitPackage( CPackForClient *pack )
CommitPackage cp;
cp.freePack = false;
cp.packToCommit = pack;
serv->sendPackToServer(cp, 255);
sendRequest(&cp, 255);
}
int CClient::getLocalPlayer() const
@ -625,7 +623,7 @@ void CClient::commenceTacticPhaseForInt(CBattleGameInterface *battleInt)
if(gs && !!gs->curB && gs->curB->tacticDistance) //while awaiting for end of tactics phase, many things can happen (end of battle... or game)
{
MakeAction ma(BattleAction::makeEndOFTacticPhase(battleInt->playerID));
serv->sendPackToServer(ma, battleInt->playerID);
sendRequest(&ma, battleInt->playerID);
}
} HANDLE_EXCEPTION
}
@ -636,6 +634,19 @@ void CClient::invalidatePaths(const CGHeroInstance *h /*= NULL*/)
pathInfo->isValid = false;
}
int CClient::sendRequest(const CPack *request, int player)
{
static ui32 requestCounter = 0;
ui32 requestID = requestCounter++;
tlog5 << boost::format("Sending a request \"%s\". It'll have an ID=%d.\n")
% typeid(*request).name() % requestID;
waitingRequest.pushBack(requestID);
serv->sendPackToServer(*request, player, requestID);
return requestID;
}
template void CClient::serialize( CISer<CLoadFile> &h, const int version );
template void CClient::serialize( COSer<CSaveFile> &h, const int version );

View File

@ -56,6 +56,58 @@ public:
~CServerHandler();
};
template<typename T>
class ThreadSafeVector
{
typedef std::vector<T> TVector;
typedef boost::unique_lock<boost::mutex> TLock;
TVector items;
boost::mutex mx;
boost::condition_variable cond;
public:
void pushBack(const T &item)
{
TLock lock(mx);
items.push_back(item);
cond.notify_all();
}
// //to access list, caller must present a lock used to lock mx
// TVector &getList(TLock &lockedLock)
// {
// assert(lockedLock.owns_lock() && lockedLock.mutex() == &mx);
// return items;
// }
TLock getLock()
{
return TLock(mx);
}
void waitWhileContains(const T &item)
{
auto lock = getLock();
while(vstd::contains(items, item))
cond.wait(lock);
}
bool tryRemovingElement(const T&item) //returns false if element was not present
{
auto lock = getLock();
auto itr = vstd::find(items, item);
if(itr == items.end()) //not in container
{
return false;
}
items.erase(itr);
cond.notify_all();
return true;
}
};
/// Class which handles client - server logic
class CClient : public IGameCallback
{
@ -75,7 +127,7 @@ public:
CScriptingModule *erm;
CondSh<int> waitingRequest;
ThreadSafeVector<int> waitingRequest;
std::queue<CPack *> packs;
boost::mutex packsM;
@ -162,6 +214,8 @@ public:
friend class CBattleCallback; //handling players actions
friend void processCommand(const std::string &message, CClient *&client); //handling console
int sendRequest(const CPack *request, int player); //returns ID given to that request
void handlePack( CPack * pack ); //applies the given pack and deletes it
void battleStarted(const BattleInfo * info);
void commenceTacticPhaseForInt(CBattleGameInterface *battleInt); //will be called as separate thread

View File

@ -717,9 +717,7 @@ void EndAction::applyCl( CClient *cl )
void PackageApplied::applyCl( CClient *cl )
{
INTERFACE_CALL_IF_PRESENT(player, requestRealized, this);
if(cl->waitingRequest.get() == packType)
cl->waitingRequest.setn(false);
else if(cl->waitingRequest.get())
if(!cl->waitingRequest.tryRemovingElement(requestID))
tlog3 << "Surprising server message!\n";
}

View File

@ -240,11 +240,11 @@ CPack * CConnection::retreivePack()
return ret;
}
void CConnection::sendPackToServer(const CPack &pack, ui8 player)
void CConnection::sendPackToServer(const CPack &pack, ui8 player, ui32 requestID)
{
boost::unique_lock<boost::mutex> lock(*wmx);
tlog5 << "Sending to server a pack of type " << typeid(pack).name() << std::endl;
*this << player << &pack; //packs has to be sent as polymorphic pointers!
*this << player << requestID << &pack; //packs has to be sent as polymorphic pointers!
}
CSaveFile::CSaveFile( const std::string &fname )

View File

@ -916,7 +916,7 @@ public:
~CConnection(void);
CPack *retreivePack(); //gets from server next pack (allocates it with new)
void sendPackToServer(const CPack &pack, ui8 player);
void sendPackToServer(const CPack &pack, ui8 player, ui32 requestID);
};
DLL_LINKAGE std::ostream &operator<<(std::ostream &str, const CConnection &cpc);

View File

@ -172,11 +172,12 @@ struct PackageApplied : public CPackForClient //94
ui8 result; //0 - something went wrong, request hasn't been realized; 1 - OK
ui32 packType; //type id of applied package
ui32 requestID; //an ID given by client to the request that was applied
ui8 player;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & result & packType & player;
h & result & packType & requestID & player;
}
};

View File

@ -627,29 +627,36 @@ void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
{
setThreadName(-1, "CGameHandler::handleConnection");
srand(time(NULL));
CPack *pack = NULL;
ui8 player = 255;
try
{
while(1)//server should never shut connection first //was: while(!end2)
{
CPack *pack = NULL;
ui8 player = 255;
si32 requestID = -999;
int packType = 0;
{
boost::unique_lock<boost::mutex> lock(*c.rmx);
c >> player >> pack; //get the package
tlog5 << "Received client message of type " << typeid(*pack).name() << std::endl;
c >> player >> requestID >> pack; //get the package
packType = typeList.getTypeID(pack); //get the id of type
tlog5 << boost::format("Received client message (request %d by player %d) of type with ID=%d (%s).\n")
% requestID % player % packType % typeid(*pack).name();
}
int packType = typeList.getTypeID(pack); //get the id of type
//prepare struct informing that action was applied
PackageApplied applied;
applied.player = player;
applied.result = false;
applied.packType = packType;
applied.requestID = requestID;
CBaseForGHApply *apply = applier->apps[packType]; //and appropriae applier object
if(packType != typeList.getTypeID<QueryReply>()
&& (packType != typeList.getTypeID<ArrangeStacks>() || !isAllowedArrangePack((ArrangeStacks*)pack)) // for dialogs like garrison
&& states.getQueriesCount(player))
if(isBlockedByQueries(pack, packType, player))
{
complain(boost::str(boost::format("Player %d has to answer queries before attempting any further actions (count=%d)!") % (int)player % states.getQueriesCount(player)));
PackageApplied applied;
applied.player = player;
applied.result = false;
applied.packType = packType;
{
boost::unique_lock<boost::mutex> lock(*c.wmx);
c << &applied;
@ -661,10 +668,7 @@ void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
tlog5 << "Message successfully applied (result=" << result << ")!\n";
//send confirmation that we've applied the package
PackageApplied applied;
applied.player = player;
applied.result = result;
applied.packType = packType;
{
boost::unique_lock<boost::mutex> lock(*c.wmx);
c << &applied;
@ -674,9 +678,8 @@ void CGameHandler::handleConnection(std::set<int> players, CConnection &c)
{
tlog1 << "Message cannot be applied, cannot find applier (unregistered type)!\n";
}
delete pack;
pack = NULL;
player = 255;
vstd::clear_pointer(pack);
}
}
catch(boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection
@ -4793,6 +4796,12 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
const CStack * attacker = gs->curB->getStack(bat.stackAttacking);
attackCasting(bat, Bonus::SPELL_AFTER_ATTACK, attacker);
if(bat.bsa[0].newAmount <= 0)
{
//don't try death stare or acid breath on dead stack (crash!)
return;
}
if (attacker->hasBonusOfType(Bonus::DEATH_STARE)) // spell id 79
{
int staredCreatures = 0;
@ -4815,6 +4824,7 @@ void CGameHandler::handleAfterAttackCasting( const BattleAttack & bat )
!attacker->attackerOwned, attacker->owner, NULL, NULL, staredCreatures, ECastingMode::AFTER_ATTACK_CASTING, attacker);
}
}
int acidDamage = 0;
TBonusListPtr acidBreath = attacker->getBonuses(Selector::type(Bonus::ACID_BREATH));
BOOST_FOREACH(const Bonus *b, *acidBreath)
@ -5621,6 +5631,22 @@ void CGameHandler::spawnWanderingMonsters(int creatureID)
}
}
bool CGameHandler::isBlockedByQueries(const CPack *pack, int packType, ui8 player)
{
//it's always legal to send query reply (we'll check later if it makes sense)
if(packType == typeList.getTypeID<QueryReply>())
return false;
if(packType == typeList.getTypeID<ArrangeStacks>() && isAllowedArrangePack((const ArrangeStacks*)pack))
return false;
//if there are no queries, nothing is blocking
if(states.getQueriesCount(player) == 0)
return false;
return true; //block package
}
CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance *army, BattleInfo *bat)
{
int color = army->tempOwner;

View File

@ -98,6 +98,7 @@ public:
std::map<ui32, boost::function<void()> > garrisonCallbacks; //query id => callback - for garrison dialogs
std::map<ui32, std::pair<si32,si32> > allowedExchanges;
bool isBlockedByQueries(const CPack *pack, int packType, ui8 player);
bool isAllowedExchange(int id1, int id2);
bool isAllowedArrangePack(const ArrangeStacks *pack);
void giveSpells(const CGTownInstance *t, const CGHeroInstance *h);

View File

@ -62,17 +62,12 @@
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">..\..</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Configuration)_VC9\</IntDir>
<_ProjectFileVersion>10.0.30128.1</_ProjectFileVersion>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(SolutionDir)</OutDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(SolutionDir)</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(Configuration)\</IntDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(SolutionDir)$(Configuration)\</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(Configuration)_VC9\</IntDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">..\..\RD</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">$(Configuration)_VC9\</IntDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(Configuration)\</IntDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">G:\Programowanie\VCMI\RD</OutDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">$(SolutionDir)$(Configuration)\bin\</OutDir>
<OutDir Condition="'$(Configuration)|$(Platform)'=='RD|x64'">$(SolutionDir)$(Configuration)\bin\</OutDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='RD|Win32'">$(Configuration)\</IntDir>
<IntDir Condition="'$(Configuration)|$(Platform)'=='RD|x64'">$(Configuration)\</IntDir>
@ -112,8 +107,8 @@
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<AdditionalDependencies>VCMI_lib.lib;zdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>../..;../../libs;../;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>VCMI_lib.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<GenerateDebugInformation>true</GenerateDebugInformation>
<TargetMachine>MachineX86</TargetMachine>
</Link>
@ -160,14 +155,13 @@
</ClCompile>
<Link>
<AdditionalDependencies>VCMI_lib.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>G:\Programowanie\VCMI\libs;$(OutDir);G:\Programowanie\VCMI\RD;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<GenerateDebugInformation>true</GenerateDebugInformation>
<GenerateMapFile>true</GenerateMapFile>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<TargetMachine>MachineX86</TargetMachine>
<Profile>true</Profile>
<OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='RD|x64'">
@ -178,7 +172,7 @@
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<OmitFramePointers>false</OmitFramePointers>
<EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations>
<AdditionalIncludeDirectories>F:\Programowanie\VCMI\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<StringPooling>true</StringPooling>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<FunctionLevelLinking>false</FunctionLevelLinking>
@ -191,12 +185,13 @@
<PrecompiledHeaderFile>StdInc.h</PrecompiledHeaderFile>
</ClCompile>
<Link>
<AdditionalDependencies>VCMI_lib.lib;zdll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>F:\Programowanie\VCMI\libs;F:\Programowanie\VCMI\RD;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<AdditionalDependencies>VCMI_lib.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(OutDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
<GenerateDebugInformation>true</GenerateDebugInformation>
<GenerateMapFile>true</GenerateMapFile>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<Profile>true</Profile>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
@ -220,7 +215,6 @@
<ItemGroup>
<ProjectReference Include="..\lib\VCMI_lib.vcxproj">
<Project>{b952ffc5-3039-4de1-9f08-90acda483d8f}</Project>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />