Help with scope caching

I am trying to deal with the scope of the player in a more optimized way so that I can have thousands of objects in the game with minimal performance problems. I am trying to use Scopability by Brady Garvin and Scope Caching by Mike Ciul in combination with some additional code and optimizations about how I reference things with understand phrases to achieve this. I’ve provided some very stripped down code from my overall application to try to show what I’m dealing with, but it’s still a ton of stuff to make it something that can show the issues.

I’m having some success, but it still seems that the “after deciding the scope of the player” is running AFTER the command is read, and that the “cache” is not having an effect on that, making it still loop through all things and causing a large delay when there are lots of things. That’s the main thing I’m trying to solve for right now… to hide the scope checking after the text of a turn is returned, right BEFORE reading a command so that the player isn’t waiting for output.

For a more info than that, read the rant if you like.

[rant]I am still considering myself kind of a noob at this language, and really would like to get to writing content, not dealing with technical issues, but I still need a lot of help. I hope I can ask this forum for help again in this somewhat major way. I’d really hate to have to abandon my ideas and the last year of trying my best to make Inform do what I want in having so many objects, but I’m at the point where if I can’t get turns to return their text results in a reasonable timeframe, I may have to give up the last year of work and quite trying.

I’m trying to include the scope caching extension by Mike Ciul in the most optimized way possible into my application. After twisting everything around a million times, it just looks like I’m going to need thousands of actual objects in my game to do what I want to do, so I’m going to need to handle scope better and this extension seems key, but I don’t know how to properly use it to achieve my goals. I finally figured out how to tweak the memory and other settings to let Inform get big enough to allow this many objects (I never knew before you could hit back and forth on the console more than once… still learning here).

I am hoping to figure a way to only run the deciding the scope of the player right BEFORE reading a command, and then never again unless another actor takes action mid-turn, so that the results of the last turn become readable quickly, and then there is a lag until the command prompt reappears. I have tried to extract all my content and custom actions, and a bunch of other stuff out of my application and just leave the parts that seemed relevant to scope and a little test content in the below code… it’s a huge example, so I don’t know if anyone will feel up to helping, but… the rules I wrote to tweak the assumptions of the included extensions all run, but it looks like the “deciding the scope of the player” is still happening AFTER reading the command at some point, and causing extreme lag when there are lots of objects.

With the tweaks I’ve made to this and with Scopability, I can get a few hundred objects in the location to not be a problem (for any command except “look” which I am waiting for zarf’s extension for the newest release with great anticipation) as long as there aren’t thousands of objects elsewhere too or they are all loose on the floor… but if the ones in the location are held by other objects, no problem. I’m just still trying my noobish best to get things to work better when there are thousands of what I call “irrelevant” objects (ones that aren’t and should never be considered in scope without some form of magic scrying in the game)…

If for some reason someone is wondering and/or concerned about why I would need something like this in the first place, thousands of objects in a game, or that it might have implications that favor AIF, I can understand your concerns… but I haven’t even begun writing any content, and all I know is that I want to create an “open world” game that is in a similar genre to The Elder Scrolls, but in text form. Could the engine include adult content because of the ability to add dozens of body parts? Sure… but it can also include a complex way of modeling very interesting creatures of many races and do nifty dismemberment stuff too, and allow for gigantic, randomized loot lists and persistent changes to the world across hundreds of rooms.

I’m trying to create a “framework” for building any game, not yet building a specific game. I don’t care how long it takes me to achieve the content goals… but if I can’t solve this technical problem of the performance sucking, I’ll never get around to that part at all…

Anyway, I’m spending months of time trying to solve this technical problem instead of working on rules that deal with game content. If anyone would like to help, please… here is my very much under construction, stripped down, and amateur attempt at solving the problem:[/rant]

Volume 1 - Settings

Use MAX_OBJECTS of 10000.
Use maximum things understood at once of at least 500.
Use MAX_PROP_TABLE_SIZE of 10000000.
Use MAX_STATIC_DATA of 10000000.
Use MAX_SYMBOLS of 1000000.
Use SYMBOLS_CHUNK_SIZE of 100000.
Use ALLOC_CHUNK_SIZE of 64000.
Use MAX_ARRAYS of 1000000.
Use MAX_NUM_STATIC_STRINGS of 1000000.
Use MAX_EXPRESSION_NODES of 1024.
Use MAX_CLASSES of 3000.
Use MAX_VERBS of 1000.
Use DICT_WORD_SIZE of 30.
Use dynamic memory allocation of at least 16384. 
Use maximum text length of at least 3000.
Use american dialect.

To consider (x - a nothing based rule):
	follow x;

Volume 2 - Managing Scope

Book 1 - Scopability by Brady Garvin

Include Scopability by Brady Garvin.

A direction can be scopable or unscopable.

When play begins:
	repeat with x running through things:
		if the holder of x is not a room and x is not a room and x is not a door:
			now x is unscopable;
	repeat with y running through directions:
		now y is unscopable;

Understand "n/north/s/south/e/east/w/west/nw/northwest/ne/northeast/sw/southwest/se/southeast/u/up/d/down/in/inside/out/outside" as "[direction]".

After reading a command (this is the put directions back in scope if actually being used rule):
	if the player's command includes "go" or the player's command includes "[direction]":
		repeat with x running through directions:
			now x is scopable;

A thing can be reset unscopable. A thing can be special scope.

Definition: a thing is reset unscopable if the holder of it is not the holder of the player and it is not special scope.

Asking_which is initially false;

Before asking which do you mean (this is the determine if asking which do you mean sequence rule):
	now asking_which is true;

Last every turn (this is the reset unscopability rule):
	repeat with x running through relevant list:
		if x is reset unscopable:
			now x is unscopable;
	repeat with y running through directions:
		now y is unscopable;
	now asking_which is false;

Book 2 - Scope Caching by Mike Ciul

Include Scope Caching by Mike Ciul.

Definition: a thing is relevant if it is in the location or the ultimate holder of it is in the location or it is special scope.

Relevant list is a list of objects that varies.

To set scope and relevant list for (x - a person):
	now trust in the scope cache is false;
	carry out the caching scope activity with x;
	now trust in the scope cache is true;	
	truncate relevant list to 0 entries;
	repeat with y running through relevant things:
		add y to relevant list, if absent;

Section - Caching Scope Before Reading a Command (in place of Section - Caching Scope After Reading a Command in Scope Caching by Mike Ciul)

Before reading a command while asking_which is false (this is the cache relevant things in relevant list rule):
	set scope and relevant list for the player;

Before an actor doing something:
	if the actor is not the player:
		set scope and relevant list for the actor;

Book 2 - Holder Scope

Chapter 1 - Multiple-Level Holder (Ultimate_Holder)

To decide which thing is the ultimate holder of (A - a thing):
	let the the_ultimate_holder be a thing;
	if the holder of A is nothing or the holder of A is a room or the holder of A is an enterable container or the holder of A is a supporter:
		decide on A;
	if A is incorporated by something:
		decide on the ultimate incorporater of A;
	if A is not incorporated by something:
		if A is held by something (called B):
			now the_ultimate_holder is B;
		if the_ultimate_holder is held by something (called C):
			now the_ultimate_holder is C;
		if the_ultimate_holder is held by something (called D):
			now the_ultimate_holder is D;
		if the_ultimate_holder is held by something (called E):
			now the_ultimate_holder is E;
		if the_ultimate_holder is not nothing:
			decide on the_ultimate_holder;
		otherwise:
			decide on A;
	otherwise:
		decide on A;

Recursive holding relates a thing (called x) to a thing (called y) when y holds x or y holds a thing that is the holder of x or y holds a thing that holds a thing that is the holder of x or y holds a thing that holds a thing that holds a thing that is the holder of x. 

The verb to recursively hold (he recursively holds) implies the recursive holding relation.

The verb to recursively held (it is recursively held) implies the reversed recursive holding relation.

Chapter 2 - Multiple-Level Incorporater (Ultimate_Incorporater)

To decide which thing is the ultimate incorporater of (A - a thing):
	let the_ultimate_incorporater be a thing;
	if A is incorporated by something:
		if A is incorporated by a thing (called B):
			now the_ultimate_incorporater  is B;
		if the_ultimate_incorporater is incorporated by a thing (called C):
			now the_ultimate_incorporater is C;
		if the_ultimate_incorporater is incorporated by a thing (called D):
			now the_ultimate_incorporater is D;
		if the_ultimate_incorporater is incorporated by a thing (called E):
			now the_ultimate_incorporater is E;
		decide on the_ultimate_incorporater;
	otherwise:
		decide on A;

recursive incorporation relates a thing (called x) to a thing (called y) when x is incorporated by y or x is incorporated by a thing that is incorporated by y or x is incorporated by a thing that is incorporated by a thing that is incorporated by y or x is incorporated by a thing that is incorporated by a thing that is incorporated by a thing that is incorporated by y. 

The verb to recursively embed (it recursively embeds) implies the recursive incorporation relation.

The verb to recursively incorporate (it is recursively incorporated) implies the reversed recursive incorporation relation.

Volume 3 - Disambiguation

Book 1 - Disambiguation Control by Jon Ingold

Include Disambiguation Control by Jon Ingold.

Chapter 1 - No Initial Expectations (in place of Chapter - Initial Expectations in Disambiguation Control by Jon Ingold

[Default assumptions are not the reason we want this extension. "The match list" being exposed to I7 is the most important thing thus far.]

Book 2 - Numbered Disambiguation Choices by Aaron Reed

Include Numbered Disambiguation Choices by Aaron Reed.

The Numbered Disambiguation Choices don't use number rule is not listed in any rulebook.

The Numbered Disambiguation Choices preface disambiguation objects with numbers rule is not listed in any rulebook.

A thing can be numbered_disambiguation_proper_cleaned.

Before asking which do you mean:
	repeat with x running through the match list:
		if x is not proper-named:
			now x is numbered_disambiguation_proper_cleaned;
			now x is proper-named;
			
After asking which do you mean:
	now every thing that is numbered_disambiguation_proper_cleaned is not proper-named;
	now every thing that is numbered_disambiguation_proper_cleaned is not numbered_disambiguation_proper_cleaned;

First before printing the name of something (called macguffin) while asking which do you mean (this is the New Numbered Disambiguation Choices preface disambiguation objects with numbers rule):
	if macguffin is not listed in match list:
		do nothing;
	otherwise:
		if disambiguation-busy is false:
			now disambiguation-busy is true;
			repeat with x running through match list:
				if x is macguffin:
					add macguffin to the list of disambiguables, if absent;
				now the disambiguation id of macguffin is the number of entries in list of disambiguables;
			say "[before disambiguation number text][the number of entries in list of disambiguables][after disambiguation number text]";

Book 3 - Disambiguating Homographic Prop_Objects

[Sometimes prop_objects will end up being grouped together because the player doesn't know any better due to the conditionals attached to understand phrases. However, beneath the surface of what the player knows, the objects are actually not identical. This section makes sure the player always has the ability to select one of the grouped items instead of having the parser simply pick the first one.]

A thing has a text called group_id. A thing can be group_id_attached. A thing is usually not group_id_attached.

Understand the group_id property as describing a thing when the group_id of the item described is not "group_id_0" and the item described is group_id_attached.

Last before reading a command:
	Let N be 1;
	repeat with x running through relevant list:
		now the group_id of x is "group_id_[N]";
		now x is group_id_attached;
		increment N;

First every turn while a thing is group_id_attached:
	repeat with x running through group_id_attached things:
		now the group_id of x is "group_id_0";
		now x is not group_id_attached;

Volume 4 - Possession and Ownership

Book 1 - Parsing Possessives

First after reading a command (this is the after manage possessives in the players command rule):
	if the player's command includes "your":
		replace the matched text with "theirs";
	if the player's command includes "my":
		replace the matched text with "your";
	let N be text;
	let N be the player's command;
	replace the regular expression "(\w)[']s" in N with "\1 [']s";
	replace the regular expression "s['](\W)" in N with "s [']s\1";
	change the text of the player's command to N;

Understand "your" as a thing when the player is the ultimate holder of the item described. Understand "theirs" as a thing when the person asked is the ultimate holder of the item described.

Volume 5 - Categories vs Kinds

Book 1 - Categories, Subcategories, and Specific Types

A category is a kind of value. Generic category is a category. A thing has a category.
A subcategory is a kind of value. Generic subcategory is a subcategory. A thing has a subcategory.
A specific type is a kind of value. Generic type is a specific type. A thing has a specific type.

Volume 5 - Knowledge

Book 1 - Knowledge Main

After reading a command:
	if the player's command matches the regular expression "(?i)_object":
		say "Cheating is not allowed. If you actually see the text '_object' in the game, please report this as a bug to the author.";
		reject the player's command;

Book 2 - Name Knowing/Recalling

Name knowing relates various people to various things. The verb to name know (he name knows, it is name known) implies the name knowing relation.

Name recalling relates a person (called x) to a thing (called y) when x is y or x name knows y. The verb to name recall (he name recalls, it is name recalled) implies the name recalling relation.

Yourself name knows yourself.

A thing has a text called known name. A thing has a text called known plural name.

Understand the known name property as describing a thing when the player name recalls the item described. Understand the known plural name property as describing a thing when the player name recalls the item described.

To say cleaned printed name of (x - a thing):
	let y be the printed name of x;
	replace the regular expression "_object" in y with "";
	say "[y]";

The known name of a thing is usually "[cleaned printed name of the item described]".

Rule for printing the name of a thing when the current speaker name recalls the item described and the current speaker is not talking about themselves:
	say "[known name]";

Rule for printing the plural name of a thing when the current speaker name recalls the item described and the current speaker is not talking about themselves:
	say "[known plural name]";

Book 3 - Current Speaker

The current speaker is a person that varies.

To decide if current speaker is talking about themselves:
	if the item described is the current speaker, yes;
	no;

To decide if current speaker is not talking about themselves:
	if the item described is the current speaker, no;
	yes;

To change the current speaker to (x - a person):
	now the current speaker is x;
	now every thing is not proper-named;
	now every thing that is name recalled by the current speaker is proper-named;
	now every thing that is not name recalled by the current speaker is not proper-named;
	repeat with y running through people that are proper-named:
		repeat with z running through things that are recursively held by y:
			now z is proper-named;

When play begins (this is the set initial current speaker rule):
	let x be the current speaker;
	change the current speaker to x;

Volume 6 - Body Parts

Book 1 - Body Parts Main

A body part is a kind of thing. body part category is a category. A body part is usually body part category.

Book 2 - Heads

Head subcategory is a subcategory.

A head is a kind of body part. A head is usually a part of every person. A head is usually head subcategory.

Understand "head/heads" as "[head]". Understand "[head]" as a thing when the item described is head subcategory.
Understand "[something related by recursive holding] 's [head]" as a thing when the item described is head subcategory.

After reading a command while the player's command includes "[head]" (this is the scope heads rule):
	repeat with x running through relevant list:
		if x is head subcategory: 
			now x is scopable;

Book 3 - Arms

Arm subcategory is a subcategory.

Some arms are a kind of body part. 20 arms are usually a part of every person. Some arms are usually arm subcategory.

Understand "arm/arms" as "[arm]". Understand "[arm]" as a thing when the item described is arm subcategory.
Understand "[something related by recursive holding] 's [arm]" as a thing when the item described is arm subcategory.

After reading a command while the player's command includes "[arm]" (this is the scope arms rule):
	repeat with x running through relevant list:
		if x is arm subcategory: 
			now x is scopable;
			
Volume 8 - Clothing

Book 1 - Clothing Main

Garment category is a category. A garment is a kind of thing. A garment is usually garment category.

Definition: a thing is wearable if it is garment category.

Understand "garment/garments/clothes/clothing" as "[clothes]". Understand "[clothes]" as a thing when the item described is garment category.
Understand "[something related by recursive holding] 's [clothes]" as a thing when the item described is garment category.

Book 2 - Headgear

Hat subcategory is a subcategory. A hat is a kind of garment. A hat is usually hat subcategory.

Understand "hat/hats" as "[hat]". Understand "[hat]" as a thing when the item described is hat subcategory.
Understand "[something related by recursive holding] 's [hat]" as a thing when the item described is hat subcategory.

After reading a command while the player's command includes "[hat]" (this is the scope hats rule):
	repeat with x running through relevant list:
		if x is hat subcategory: 
			now x is scopable;			

Volume 10 - Test Game

The kitchen is a room.

The player is in the kitchen.

Bob_object is a man in the kitchen. yourself name knows Bob_object.

Sally_object is a woman in the kitchen.

1 hat is in the kitchen.

There are 100 people.
There are 100 people.

Ok, so I’ve gone through and removed a bunch of rules with “does nothing” phrases, and to my surprise, I could remove almost every rule that was running, and still do “look bob” successfully. It doesn’t look like any rule in particular is causing the slowdown, because I had things stripped bare… basically, I turned on “rules” and looked at the output to visually scan for where the slowdown was, and I got things to the point where the slowdown happened before any “such and such rule applies” was printed at all.

So I turned on trace 6, and it looks like what is happening, is that despite all my best efforts adding all these scoping extensions and trying to isolate things, that the slowdown is still in the parser itself. Everywhere that “Outside parsing routine returned X” occurs, there is a major lag when there are lots of things. How can that be mitigated?

Alright, well, no one responded, but nevertheless… after working on this more myself, preliminary results are starting to point to the multiple-level holder and multiple-level incorporation decision phrases and relations. I had wanted to make it so that if a creature were dismembered or an object disassembled or partially destroyed, that subset parts and contents would follow along with it by being already related to it that way… but I think that this was being called in the parser routine because the understand phrases were using those relations.

This was very confusing, because no rule was listed during “rules” that was using this, the parser was using it really deep behind the scenes. I did need Scopability, but so far, I haven’t found a place for Scope Caching since removing multiple-level holding. Now that everything is only one level deep, with the Scopability and relevant list trickery I had before, even having thousands of things is running with acceptable performance.

I have to watch out for those “understand” phrases I guess, and write custom rules for having appropriate parts from an intuitive perspective behave as such without actually being multiply-incorporated going forward. That’s quite a pain, and it was hard for me to figure out… but if I’m not missing something and it is working as much better as my initial test show, I think I have rooted this cause out.