Skip to content

Commit

Permalink
Write variable documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
gabber235 committed Nov 10, 2024
1 parent fc19155 commit 9188437
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 4 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import CodeSnippet from "@site/src/components/CodeSnippet";
import Figure from "@site/src/components/Figure";

# VariableEntry

The `VariableEntry` is designed to make your entries more dynamic by allowing fields to be computed at runtime. Instead of hardcoding values in your entries, you can use variables that adapt based on the current game state, player context, or other conditions.

## Understanding Variables

When creating entries in Typewriter, you often need fields that shouldn't be static. For example, you might want a text that changes based on a player's progress, or a location that depends on where certain events happened in the game. This is where `VariableEntry` comes in - it acts as a dynamic value provider that can return different values based on the current context.

### Basic Implementation

Let's start with a simple variable entry:

<CodeSnippet tag="variable_entry" json={require("../../../snippets.json")} />

Every variable entry receives a `VarContext` when it needs to provide its value. This context contains:
- The current player (`context.player`)
- The expected return type (`context.klass`)
- Any associated data for this specific usage (`context.data`)

### Adding Variable Data

Variables become more powerful when they can be configured differently at each usage site. This is achieved through variable data:

<CodeSnippet tag="variable_entry_with_data" json={require("../../../snippets.json")} />

The relationship between entry fields and variable data is important to understand:

1. Entry Fields (like `someString`):
- Defined when creating the variable entry
- Remain the same for all uses of this variable entry instance
- Used for configuration that should be consistent

2. Variable Data Fields (like `otherInfo`):
- Can be different every time the variable is used
- Provide context-specific configuration
- Allow the same variable entry to behave differently in different situations

<div className="flex flex-col items-center justify-center space-y-4 md:space-y-0 md:flex-row md:space-x-2">
<Figure title="Variable Entry" img={require("./assets/variable/example_variable_with_data.png")} alt="Variable Entry with Data" width={400} />
<Figure title="Variable Usage" img={require("./assets/variable/example_var_with_data_used.png")} alt="Variable Usage with Data" width={400} />
</div>

### Working with Generic Types

Sometimes you need variables that can work with different types of values. The generic system in `VariableEntry` makes this possible:

<CodeSnippet tag="generic_variable_entry" json={require("../../../snippets.json")} />

When working with generics, remember:
- A single instance of a variable entry will always return the same type
- Different instances of the same variable entry can work with different types
- When your variable data uses a generic type, the variable entry must have at least one field using that same generic type
- This ensures that the generic type information flows correctly from the usage site through the data to the entry

<div className="flex flex-col items-center justify-center space-y-4 md:space-y-0 md:flex-row md:space-x-1">
<Figure title="String Generic Variable" img={require("./assets/variable/generic_string.png")} alt="String Generic Variable" width={500} />
<Figure title="Duration Generic Variable" img={require("./assets/variable/generic_duration.png")} alt="Duration Generic Variable" width={500} />
<Figure title="Generic Variable Usage" img={require("./assets/variable/generic_used.png")} alt="Generic Variable Usage" width={500} />
</div>

### Type Constraints

You can restrict which entry fields your variable can be used with:

<CodeSnippet tag="constraint_variable_entry" json={require("../../../snippets.json")} />

The `@GenericConstraint` annotation specifies which types of fields this variable can replace. In the example above, this variable can only be used for `String` or `Int` fields in other entries. This ensures that variables are only used in appropriate contexts within your content.


## Variable Entry Usage

Now that you understand the basics of `VariableEntry`, let's see how we can mark fields to allow for dynamic values.

<CodeSnippet tag="variable_usage" json={require("../../../snippets.json")} />

In this example, we have a variable entry that has two fields: `someString` and `someInt`.
The `Var` type is used to represent a variable that can be used in different contexts.

:::caution
**Not all fields should be marked as variables.** Only fields where it would make sense to have a dynamic value should be marked as a variable.
:::

2 changes: 1 addition & 1 deletion documentation/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 21 additions & 1 deletion documentation/plugins/code-snippets/snippets.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,27 @@
},
"speaker_entry": {
"file": "src/main/kotlin/com/typewritermc/example/entries/static/ExampleSpeakerEntry.kt",
"content": "@Entry(\"example_speaker\", \"An example speaker entry.\", Colors.BLUE, \"ic:round-spatial-audio-off\")\nclass ExampleSpeakerEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n override val displayName: String = \"\",\n override val sound: Sound = Sound.EMPTY,\n) : SpeakerEntry"
"content": "@Entry(\"example_speaker\", \"An example speaker entry.\", Colors.BLUE, \"ic:round-spatial-audio-off\")\nclass ExampleSpeakerEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n override val displayName: Var<String> = ConstVar(\"\"),\n override val sound: Sound = Sound.EMPTY,\n) : SpeakerEntry"
},
"variable_entry": {
"file": "src/main/kotlin/com/typewritermc/example/entries/static/ExampleVariableEntry.kt",
"content": "@Entry(\"example_variable\", \"An example variable entry.\", Colors.GREEN, \"mdi:code-tags\")\nclass ExampleVariableEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n) : VariableEntry {\n override fun <T : Any> get(context: VarContext<T>): T {\n val player = context.player\n val klass = context.klass\n\n TODO(\"Do something with the player and the klass\")\n }\n}"
},
"variable_entry_with_data": {
"file": "src/main/kotlin/com/typewritermc/example/entries/static/ExampleVariableEntry.kt",
"content": "@Entry(\"example_variable_with_data\", \"An example variable entry with data.\", Colors.GREEN, \"mdi:code-tags\")\n// Register the variable data associated with this variable.\n@VariableData(ExampleVariableWithData::class)\nclass ExampleVariableWithDataEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n // This data will be the same for all uses of this variable.\n val someString: String = \"\",\n) : VariableEntry {\n override fun <T : Any> get(context: VarContext<T>): T {\n val player = context.player\n val klass = context.klass\n this.someString\n val data = context.getData<ExampleVariableWithData>() ?: throw IllegalStateException(\"Could not find data for ${context.klass}, data: ${context.data}\")\n\n TODO(\"Do something with the player, the klass, and the data\")\n }\n}\n\nclass ExampleVariableWithData(\n // This data can change at the place where the variable is used.\n val otherInfo: Int = 0,\n)"
},
"generic_variable_entry": {
"file": "src/main/kotlin/com/typewritermc/example/entries/static/ExampleVariableEntry.kt",
"content": "@Entry(\"example_generic_variable\", \"An example generic variable entry.\", Colors.GREEN, \"mdi:code-tags\")\nclass ExampleGenericVariableEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n // We determine how to parse this during runtime.\n val generic: Generic = Generic.Empty,\n) : VariableEntry {\n override fun <T : Any> get(context: VarContext<T>): T {\n val player = context.player\n val klass = context.klass\n\n // Parse the generic data to the correct type.\n val data = generic.get(klass)\n\n TODO(\"Do something with the player, the klass, and the generic\")\n }\n}\n\nclass ExampleGenericVariableData(\n // Generic data will always be the same as the generic type in the variable.\n val otherGeneric: Generic,\n)"
},
"constraint_variable_entry": {
"file": "src/main/kotlin/com/typewritermc/example/entries/static/ExampleVariableEntry.kt",
"content": "@Entry(\"example_constraint_variable\", \"An example constraint variable entry.\", Colors.GREEN, \"mdi:code-tags\")\n@GenericConstraint(String::class)\n@GenericConstraint(Int::class)\nclass ExampleConstraintVariableEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n // We determine how to parse this during runtime.\n val generic: Generic = Generic.Empty,\n) : VariableEntry {\n override fun <T : Any> get(context: VarContext<T>): T {\n val player = context.player\n // This can only be a String or an Int.\n val klass = context.klass\n\n // Parse the generic data to the correct type.\n val data = generic.get(klass)\n\n TODO(\"Do something with the player, the klass, and the generic\")\n }\n}"
},
"variable_usage": {
"file": "src/main/kotlin/com/typewritermc/example/entries/static/ExampleVariableEntry.kt",
"content": "@Entry(\"example_action_using_variable\", \"An example action that uses a variable.\", Colors.RED, \"material-symbols:touch-app-rounded\")\nclass ExampleActionUsingVariableEntry(\n override val id: String = \"\",\n override val name: String = \"\",\n override val triggers: List<Ref<TriggerableEntry>> = emptyList(),\n override val criteria: List<Criteria> = emptyList(),\n override val modifiers: List<Modifier> = emptyList(),\n val someString: Var<String> = ConstVar(\"\"),\n val someInt: Var<Int> = ConstVar(0),\n) : ActionEntry {\n override fun execute(player: Player) {\n val someString = someString.get(player)\n val someInt = someInt.get(player)\n\n // Do something with the variables\n }\n}"
},
"action_entry": {
"file": "src/main/kotlin/com/typewritermc/example/entries/trigger/ExampleActionEntry.kt",
Expand Down
4 changes: 2 additions & 2 deletions documentation/src/components/Figure/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ interface FigureProps extends Props {

export default function Figure({ title, ...props }: FigureProps) {
return (
<figure className="flex flex-col items-center justify-center space-y-2">
<div className="flex flex-col items-center justify-center space-y-2">
<Image {...props} />
<figcaption className="text-center text-gray-500 dark:text-gray-400">{title}</figcaption>
</figure>
</div>
);
}

0 comments on commit 9188437

Please sign in to comment.