General DLL programming questions

If you're going to wind up calling it a lot, you may want to cache it (once per turn say) for performance reasons.
Nice and elegant. I like it. However, how then would I cache this so that it can be updated once per turn? This could be called quite a bit.

It's also very similar to some calculations that I'll need to make for AI purposes eventually so it'd be good to start understanding this caching stuff a bit better.

together with a new Plot Unit Function (PUF, which you'll find a palette of defined in CvGameCoreUtils.cpp):
I'll take a look there and see if I can make sense of it. I can see that you mean there's an intermediate step (or more) between the first call and the second displayed function but it seems to make sense initially. I'll let you know if I run into any trouble with that.

Thanks btw! This is good stuff!
 
Nice and elegant. I like it. However, how then would I cache this so that it can be updated once per turn? This could be called quite a bit.

It's also very similar to some calculations that I'll need to make for AI purposes eventually so it'd be good to start understanding this caching stuff a bit better.

For any function that just returns a fixed value for a given param, which you're happy to continue using whatever value you calculate on the first call to it each turn, an idiom like this works well:
  1. Add to the containing object (in this case CvPlayer I guess) member variables for your cache:
    Code:
    std::map<PromotionsTypes,int> m_afflictionCountCache;
    int m_afflictionCountCachedTurn;
    You may wish to declare these member variables 'mutable' if the function that is going to calculate and return the value is logically 'const' (and you want to declare it as such)
  2. Clear the cache on reset() of the containing object (so in CvPlayer::reset() here:
    Code:
    m_afflicationCountCachedTurn = -1;
    m_afflictionCountCache.clear();
  3. In the method that calculates the value:
    Code:
    int iResult;
    
    if ( m_afflicationCountCachedTurn != GC.getGame().getGameTurn())
    {
        m_afflictionCountCache.clear();
        m_afflicationCountCachedTurn != GC.getGame().getGameTurn();
    }
    
    std::map<PromotionTypes,int>::const_iterator itr = m_afflictionCountCache.find(eAfflication);
    if ( itr == m_afflictionCountCache.end() )
    {
        // Calculate from first principles leaving the result in iResult
        ...
    
        m_afflictionCountCache.add(std::make_pair(eAfflication,iResult));
    }
    else
    {
        iResult = itr->second;
    }
    
    return iResult;
 
Ok. I think I can follow that. Thanks!
Mind the calling context. If it can be both async and sync then you must not add to the cache from the async call unless the cache value is always accurate (so the cache is properly cleared whenever something happens that might change the value that you would calculate without the cache).
 
Mind the calling context. If it can be both async and sync then you must not add to the cache from the async call unless the cache value is always accurate (so the cache is properly cleared whenever something happens that might change the value that you would calculate without the cache).

Ok, so what I'm understanding here is that the value would re-compile and reset only once per round, at the beginning of each player's turn. Even if the actual check to this function could find an inaccurate report as things may have changed since the beginning of the turn, its always going to simply get whatever the value was at the beginning of the turn instead. (A very tolerable margin of error imo.)

This will, at least for now though I can see some possible additional uses for this down the road in other AI programming routines, currently be used for assisting AI units in determining the value of taking a Cure Affliction enabling promotion. Is it somehow possible that this kind of evaluation would ever be called in an async manner?

Even if it wouldn't be ever be called async via this use, should I nevertheless be setting things up so that a call to this value (a count of how many units the owner possesses that has a given Affliction) CAN somehow be safely referenced from an async source just in case such a situation was possible in further uses of this function down the road?

You're saying I can't add to the cache during an async call, but the only time the data would compile and cache would be during the player's turn setup, right? So that can't ever be caching during an async call? Am I right or am I missing something here about what you're telling me?
 
Ok, so what I'm understanding here is that the value would re-compile and reset only once per round, at the beginning of each player's turn. Even if the actual check to this function could find an inaccurate report as things may have changed since the beginning of the turn, its always going to simply get whatever the value was at the beginning of the turn instead. (A very tolerable margin of error imo.)
The caching technique that Koshling posted the code for up there does NOT calculate the value for all afflictions at the beginning of the turn. It calculates them the first time the value is requested which might be at some other time and it might be at different times for both players if this function is called in async context.
Then it only clears the cache at turn start.

This will, at least for now though I can see some possible additional uses for this down the road in other AI programming routines, currently be used for assisting AI units in determining the value of taking a Cure Affliction enabling promotion. Is it somehow possible that this kind of evaluation would ever be called in an async manner?
Not sure, you will have to check. The unit value functions are used in async context sometimes but it might be that the promotion ones are not.

Even if it wouldn't be ever be called async via this use, should I nevertheless be setting things up so that a call to this value (a count of how many units the owner possesses that has a given Affliction) CAN somehow be safely referenced from an async source just in case such a situation was possible in further uses of this function down the road?

You're saying I can't add to the cache during an async call, but the only time the data would compile and cache would be during the player's turn setup, right? So that can't ever be caching during an async call? Am I right or am I missing something here about what you're telling me?
See my first answer.
Personally I think it might be better to keep an accumulated value of the count on CvPlayer at all times unless you only need this kind of information for a small subset of the number of promotions. Increase the count when a unit is promoted with that promotion and decrease it when it is killed or loses the promotion.
 
Personally I think it might be better to keep an accumulated value of the count on CvPlayer at all times unless you only need this kind of information for a small subset of the number of promotions. Increase the count when a unit is promoted with that promotion and decrease it when it is killed or loses the promotion.

This works fine too. Make sure you add recalculation of the accumulated value to modifier recalc though if you go this route.
 
oops double posted

But some clarification on this:
Make sure you add recalculation of the accumulated value to modifier recalc though if you go this route.
would be good.

I understand the basic idea of what you're saying here I guess so I'll take a look at the modifier recalc to see what I can make of what you mean. Have I been missing this step with some of the previously array data in CvUnit that would work in a similar manner?
 
Personally I think it might be better to keep an accumulated value of the count on CvPlayer at all times unless you only need this kind of information for a small subset of the number of promotions. Increase the count when a unit is promoted with that promotion and decrease it when it is killed or loses the promotion.
This is similar to the method I use to track the Afflictions on given units without having to cycle through all promotions tons of times. This one makes a lot of sense to me and I had been thinking of doing something like that actually. I may, in fact, go this route. Nevertheless, this whole conversation has been extremely enlightening so its been quite helpful to see the different approaches.

The one concern I had about utilizing this method was that I don't think the Keyed info setups have been established in CvPlayer as they have in CvUnit and I'm a bit concerned I might miss a step or make a mistake in setting up a vaguely understood process - otherwise I'll need to utilize an Array and I fear that would be less than satisfactory for y'all. But I think I CAN figure out how to set up a Keyed info reference like has been utilized to convert all those CvUnit arrays.

The caching technique that Koshling posted the code for up there does NOT calculate the value for all afflictions at the beginning of the turn. It calculates them the first time the value is requested which might be at some other time and it might be at different times for both players if this function is called in async context.
Then it only clears the cache at turn start.
Ah... that makes sense then. So in case its used somewhere that might call for the information in an async manner then it could be a setup for a problem if the sync situation isn't considered. I had misunderstood the full intention there. Nevertheless, what if I DID run a check, just to enforce the caching, at the beginning of each round for each player? I suppose I wouldn't want to do that though considering that some player rounds might be able to get around having to run it at all so why bother processing what needs not be processed huh?

hmm... nevertheless, this helps in gleaning some further understanding of some of our possible sources of OOS issues.
 
I have two questions, related to the new Tech Commerce Modifiers I'm trying to add.

  1. How would I apply this for Culture? I have already done it for Research and Gold, but Culture seems to elude me.
  2. Have I made this save-compatable? I needed to add three new variables to track the cumulative tech commerce modifiers on teams, and I'd appreciate if Koshling could look at that to make sure I haven't borked any saves.

I've committed the modified source files (in a branch), and have commented with

Code:
//ls612: Tech Commerce Modifiers

my changes. Thanks in advance for the help. :goodjob:
 
I'm a little curious how you set this up in the first place given that you're asking the first question there. I've been finding, in generating Commerce tags for traits, that its only in some text manager areas and some AI, where it becomes necessary to set things up differently for each. It sounds as if you're trying to do this in a manner that is not an array, which is a fairly different method to what's been done elsewhere. I'd be happy to take a look and see what it is you're stuck on there.

I'd also be glad to look over the savegame issues as well. Koshling knows a great deal MORE about this than I do but I have needed enough of a functional understanding of it to get this far so I should be able to provide proper diagnosis there.
 
I'm a little curious how you set this up in the first place given that you're asking the first question there. I've been finding, in generating Commerce tags for traits, that its only in some text manager areas and some AI, where it becomes necessary to set things up differently for each. It sounds as if you're trying to do this in a manner that is not an array, which is a fairly different method to what's been done elsewhere. I'd be happy to take a look and see what it is you're stuck on there.

I'd also be glad to look over the savegame issues as well. Koshling knows a great deal MORE about this than I do but I have needed enough of a functional understanding of it to get this far so I should be able to provide proper diagnosis there.

I'm applying it globally to the commerces. Specifically, Gold gets it in the calculateBaseNetGold() method on CvPlayer (same as I added gold modifiers by gamespeeds), and Research gets it on calculateResearchModifier().
 
Yeah, I thought you might've been going about it in that way. So, in this method, each commerce has its own tag right?

There's a better way to do this that would get you entirely around your concern with each individual commerce.

From what I surmise from what you've stated so far, you're attempting to give a base commerce value adjustment in all cities based on tech achievements right? Or you COULD be looking to create a commerce modifier (%) that stems from tech achievements. But so far what you've stated suggests the first.

In that case, the following line in CvTeam under processTech should be the way to go:

Code:
	for (iI = 0; iI < MAX_PLAYERS; iI++)
	{
		if (GET_PLAYER((PlayerTypes)iI).getTeam() == getID() && GET_PLAYER((PlayerTypes)iI).isAlive())
		{
			for (int iJ = 0; iJ < NUM_COMMERCE_TYPES; iJ++)
			{
				GET_PLAYER((PlayerTypes)iI).changeFreeCityCommerce(((CommerceTypes)iJ), GC.getTechInfo(eTech).getCommerceChange(iJ) * iChange);
			}
		}
	}
	Note that the first 2 lines there are already set up in CvTeam so its only the rest of it that should go within the bracket after:
	if (GET_PLAYER((PlayerTypes)iI).getTeam() == getID() && GET_PLAYER((PlayerTypes)iI).isAlive())
That might jog some insight into the rest of the process. Via this mechanism, you'd simply need to set up the array in TechInfos (in the schema and info files - and you can follow the original civic tag CommerceChanges for that). Then you'd need to utilize the same kind of cloning technique to sort out how to work up the text file. You should actually not have ANY problem with savegame compat this way because you're plugging things into preexisting variable streams and another nice side effect is NOT needing to mess with anything more than the load, plugin(the portion above) and text displays. The processing pathways are already all established and the numeric values will load into the player details as if it were coming from a civic change or trait.

EDIT: You WILL need to also consider how to work the new tag into the AI tech selection structure and that's where I can't help much at this time.
 
No, that's not quite what I want to do. I have 3 separate tags, one for Gold, Science, and Culture that I want to put on techs, not one tag to increase all commerce. I'm going to work on the two I have working for now and leave Culture alone until someone can help me figure this out.
 
hmm... either you misunderstand me or I misunderstand your intention for the way the tag(s) will play out. Before I try to re-explain anything I'm getting at here, let me ask you this:

What, exactly, are you looking for these tags to DO? Are you trying to say its a Commerce income adjustment to the player as a whole (per round), not to every city that player possesses?
 
Ok, I ran across where you started discussing this project and I get what you're trying to do now.

I'm going to make Tech Commerce Modifier tags that should allow us to express the Language civics as technological advances, ie the bonuses will remain the same but will come on techs.
So you're looking for a Tech Commerce Modifier which is a % rate modifier for the whole nation. You say it yourself that you're looking to achieve the same effect from earning a civic that comes from selecting a particular tech.

So the way to go about that is to make it go ahead and utilize most of the pre-existing programming that is already taking place via the same pathways that the Civic tag(s) is/are using. Now that I have a better idea of what you're trying to achieve, I can tell you that the tag used in the civics is:
<element type="CommerceModifiers" minOccurs="0"/>

Which is declared in the schema by:
Code:
	<ElementType name="iCommerce" content="textOnly" dt:type="int"/>
	<ElementType name="CommerceModifiers" content="eltOnly">
		<element type="iCommerce" minOccurs="0" maxOccurs="*"/>
	</ElementType>
Careful to search for iCommerce in the tech schema cuz its probably already declared elsewhere (but if not its necessary to declare it as done above.)

Then basically copy over to tech infos the same loading steps used for the civics (in CvInfos.h and CvInfos.cpp). I assume you'll find that part easy but if you'd like I'd be happy to go through the steps there for you as well. However, there's an interesting quirk here in CvInfos.h (and is included in some of the steps in CvInfos.cpp) that interacts with the text manager. Instead of just int getCommerceModifier(int i) const; you also have a call to the whole array as such:
Code:
	int getCommerceModifier(int i) const;
	int* getCommerceModifierArray() const;
and then you don't need more than the usual declaration for the underlying variable:
int* m_piCommerceModifier;

Then, much like above,
Code:
	for (iI = 0; iI < MAX_PLAYERS; iI++)
	{
		if (GET_PLAYER((PlayerTypes)iI).getTeam() == getID() && GET_PLAYER((PlayerTypes)iI).isAlive())
		{
			for (int iJ = 0; iJ < NUM_COMMERCE_TYPES; iJ++)
			{
				GET_PLAYER((PlayerTypes)iI).[COLOR="Red"]changeCommerceRateModifier[/COLOR](((CommerceTypes)iJ), GC.getTechInfo(eTech).[COLOR="Red"]getCommerceModifier[/COLOR](iJ) * iChange);
			}
		}
	}
	Note that the first 2 lines there are already set up in CvTeam so its only the rest of it that should go within the bracket after:
	if (GET_PLAYER((PlayerTypes)iI).getTeam() == getID() && GET_PLAYER((PlayerTypes)iI).isAlive())
Note the red is all that's different from the previous example I gave, in this case for the global % modifier. When I say Global in this case, I mean for the whole nation, not for all civs.

And as for processing? Done and done. This leaves just the text and ai remaining. I can help with the text but as I said, tech ai is something I haven't looked into much. I could sort that out too though with enough time and if you leave it for now, either Koshling will come around and instruct you there or I will figure it out (which I will sooner or later) and let you know (or you may have been able to sort it out yourself by then.)

So for text, this method makes the Text Manager MUCH easier too. It's basically a matter of following the Civic tag (or Trait tag as this exists for traits even before my trait tags)

You'll find this simple line under parseCivic:

Code:
	//	Commerce Modifier
	setCommerceChangeHelp(szHelpText, L"", L"", gDLL->getText("TXT_KEY_CIVIC_IN_ALL_CITIES").GetCString(), GC.getCivicInfo(eCivic).getCommerceModifierArray(), true);
So all you need to do is add that in the parseTech function. Something like:
Code:
	//	Commerce Modifier
	setCommerceChangeHelp([COLOR="SeaGreen"]szHelpText[/COLOR], L"", L"", gDLL->getText("TXT_KEY_CIVIC_IN_ALL_CITIES").GetCString(), GC.getTechInfo(eTech).getCommerceModifierArray(), true);
The green is a watch spot... sometimes that call can be different in the various parse functions. However, you DON'T usually have to change the text reference. Might want to search for it and take a look at how it displays but usually it'll say exactly what you want it to without any further effort (only now does so under tech definitions.)

You also might want to edit parseCommerceHelp to get specific as to where this value is coming from in the help popups by subtracting out your modifier's values from the base value it would normally be included in (coming from cvplayer) and add it separately so that it displays as a differing value that is included in the total. You can see there how Civics and Traits are divided up and displayed separately on the popup lists. You can follow the same example there with the Tech tag.



I know that you would rather stay within what is more basic, one tag for one value, but this mechanism is set up to make it much easier for you and to go about it with a tag for each commerce is really a painful reinventing of the wheel. It'd probably also make the AI portion more difficult as well, though I can't say that for sure. Additionally, this keeps things more open. Say, for example, someone rather bravely (not a plan of mine I assure you) decides to add a new Commerce to the game. It'd be ready for that too. But more importantly, this saves a LOT of coding work as the Civic and Trait tags are already plugging their values in everywhere those values should go so adding your new spring to that data river will ensure that all places that your data should plug in will naturally do so along the routes already defined.

Best of all, going about it this way has zero risk of savegame incompatibility.

And to make sure there's no misunderstanding here, the way the tag is expressed in the techinfos file would enable you to work with specific commerces, not all at once (necessarily.) It looks like this:
Code:
			<CommerceModifiers>
				<iCommerce>0</iCommerce>
				<iCommerce>0</iCommerce>
				<iCommerce>20</iCommerce>
				<iCommerce>0</iCommerce>
			</CommerceModifiers>
This would produce an extra 20% culture. The last one is unnecessary to list off as its just a 0 for Espionage, which would be the default if its missing, but I listed it here nevertheless just for the sake of the example.
 
@Thunderbrd:

Great! This is much easier, and for once I can appreciate your verbosity. :p Only problem is that there is no parseTech function, so I think the Tech help strings are built differently. How would I go about making tech help then?

Also, I think I can handle the AI myself, so that should not be an issue.
 
After a glance through (I may take more time to look into this later but I'm pressed for time atm...) I believe the tech tags are displayed under the control of the function:
setTechTradeHelp

Take a look there - it looks like it covers everything correctly there. It's a bit of an odd naming for the function but since it all seems to be calling what I'd expect it to call then its a good bet that's the correct location to insert your text coding.
 
@Thunderbrd:

So I've added everything, including the schema changes, but when loading the game to try it out I get an XML load error with no explanation. I figure it has to be something in the load process, but I basically copy-pasted that from the building Commercemodifiers and only changed what class they belonged to. There aren't any duplicate Tech_Schemas with the same name.

I'd have to suspect that I messed up the array loading code, but I don't see how I could have done that because I just copy-pasted it from the buildinginfos to the techinfos. :confused: What did I do wrong?
 
Back
Top Bottom