Advanced topics

This page covers advanced FVS topics.

Need a hand?

Feel free to join our Discord server and we'll more than happily help you.

Pattern

Terminology

Patterns define format and therefore, composition of version IDs.

Each pattern is consisting of multiple segments, ordered from the most significant (on the left) to the least significant (on the right).

Significance means how much the segment is changed, compared to others.

Imagine a number - 00. Now, add 1 and see what happens - 01, 02, ... 08, 09, 10, 11, ... In this case, we can call the right digit the least significant, as it is changing most often. The digit on the left is more significant than that digit, because it changes less often - as there's nothing else on the left, this is also the most significant digit.

Each segment consists of an ordered sequence of elements, exactly how they should gradually appear in version IDs. Elements can not only be numbers, but also strings.

Segments can either be dynamic (consisting of 2 or more elements) or static (consisting of only 1 element - they do not change).

Designing a pattern

Individual dynamic segments should be divided from each other by a static segment. The static segments should only contain characters, which do not appear in any other dynamic segment - strictly denoting the pattern and preventing from overlapping segments.

Let's assume following pattern (which you might probably be using for your plugin currently) - A.B, where A and B are numbers, which will be changing. So, we can say we have 3 segments - dynamic represented by A, static containing the dot and then dynamic represented by B.

Now, we would like B to only represent digits 0-9 and A starting at 1 and going, theoretically, to infinity.

We can specify A and B using range constructors; the dot with element constructor. The final code would look like this:

new Pattern(
    Segment.range(1, Integer.MAX_VALUE), Segment.literal("."), Segment.range(0, 10)
);

The oldest version ID specified by the pattern would be with A and B replaced with the first element - 1 and 0 respectively; standing for 1.0 ID.

According to the iteration description below, here are the version IDs specified by this pattern:

1.0 -> 1.1 -> 1.2 -> 1.3 -> ... -> 1.7 -> 1.8 -> 1.9 -> 2.0 -> 2.1 -> ... -> 2.9 -> 3.0 -> etc.

Confused or stuck?

Feel free to contact us at https://dejvokep.dev/#contact. Quick replies and superior attitude.

Alternatively, just define an easy pattern consisting of only one part, representing range from 1 to infitinity: new Pattern(new Part(1, Integer.MAX_VALUE)).

Segments

There are 2 types of segment specifications:

  • range - the segment represents an ordered sequence of integers, specified by the given range,

  • literal - the segment represents the ordered sequence (array) of strings given.

Range segments:

Segment.range(int start, int end)
Segment.range(int start, int end, int step)
Segment.range(int start, int end, int step, int fill)

Range is specified by start and end integer, with start (inclusive) representing the first element in the resulting sequence, end (exclusive) the last element in the sequence.

You can also define step (= difference) between each element in the range using the step parameter. Please remember that it must be specified in the direction of the range - it cannot be equal to 0, if start < end -> step > 0 and if start > to -> step < 0. Always follow the method documentation. If step is not given, difference between each element will be 1 (or -1 in case of a descending range).

You can also use fill parameter which adds zeros at the start of each integer, until it has the desired length specified by the parameter. If there is any integer naturally overflowing the parameter (e.g. the range contains integer 100 and fill is set to 2), an exception will be thrown.

Literal segments:

Such segments represent the ordered sequence of string literals as given.

Segment.literal(String... elements)

Iteration

Parsed ID (by a pattern) results in a Version object (in-code representation of an ID).

The object contains array of indexes for each segment (as specified by the pattern, so the first index in the array corresponds to the first segment in the pattern...), representing the index of the element in the segment's sequence we are currently at.

That means, for ID 1.2 and pattern setup explained above, the cursors (indexes) look like this:

[0, 0, 2]

Meaning, we are:

  • at the 1st element of the first part - 1

  • at the 1st element of the second (static) part - .

  • at the 3rd element of the third part - [0, 1, 2, 3, ... 9].

The updater iterates through all version IDs starting from the ID of the user file, until it reaches the ID of the default file. The updater is only able to iterate forwards (to the next version ID in the row, not previous) - also called shifting.

When moving to the next version ID, the index representing the least significant part is shifted by one. If it was the last index in the segment's sequence, it is reset back to 0 (first element in the sequence) and the same process is repeated for the segment to the left.

Examples:

1) Let's simulate shifting from 1.2, seeing the index configuration above. The index of the least significant segment is shifted to 3. As index 3 is still valid (the part has 0-9 -> 10 elements), the shifting finished. The indexes now look like this:

[0, 0, 3]

Effectively representing the first element in the first segment (most significant), same goes for the 2nd part (the static one). The index for the last part is set to 3, representing the 3rd element in the part's sequence - 3. Therefore, the version ID represented is 1.3.

2) Let's shift from 1.9. The indexes look like this:

[0, 0, 9]

Now, the index of the least significant segment is shifted: 9 -> 10. As 9th index was the last (represents the last element in the sequence), it is reset to 0. Now we continue with the segment to the left.

(current index configuration: [0, 0, 0])

This segment is static, so it only has one element. We shift the index to 1. As 0 was index of the last (and only) element in the sequence, we reset the index to 0 and move to the left.

(current index configuration: [0, 0, 0])

We shift the index from 0 -> 1. Index 1 is valid and represents an element in the segment's sequence. The shifting finished; with the final index configuration looking like this:

[1, 0, 0]

Effectively representing ID of 2.0. Yay! 🎉

Limitations and good practices

Persistence

Once you chose a pattern, you cannot change it's composition.

Never skip version IDs

It is not required that each new revision of a file should have the right next ID specified by the pattern (e.g. 1.2 and then 1.3 for the new revision).

Generally, it just required that the new revision has n-th next ID specified by the pattern - you can skip some (e.g. after 1.2 it might be 1.4, 1.5...) - however, it is considered a bad practice (wastes iteration cycles).

First revisions should always have the first ID defined by the pattern

The first revision of the file using this library should always have the ID equal to the first ID specified by the pattern (1.0 in the examples above).

Versioning types

There are 2 types of FVS:

  • manual

  • automatic

Manual FVS

Documentation of the underlying Java class: click.

If using Manual FVS, you need to supply version ID of both the user and default file and a pattern. The given IDs are immediately parsed using the given pattern.

The version ID of the default file given must always be valid against the pattern. So should the ID of the user file be, too. However, the ID of the user file can also be null (not found, unknown) and does not need to be valid (malformed by the user...) - implemented mainly for the needs of automatic versioning.

In such case, the user file version ID is treated as the first defined by the pattern.

Manual FVS is hard to maintain; use automatic instead.

Manual is available only for custom versioning systems, in case you want to supply the version IDs yourself.

Automatic FVS

Documentation of the underlying Java class: click.

Recommended.

Automatic FVS requires both the user and default file to have their version IDs specified inside at a certain route, for example:

...
config-version: 1.2
...

On top of that, you just need to specify that route (at which the IDs can be found) and a pattern, using which the IDs will be parsed. The system will automatically obtain and validate the version IDs during runtime.

The default file must have it's version ID specified inside the file and it must be valid (parsable by the pattern).

Unlike version ID of the defaults, ID of the user file is open and vulnerable to modifications from the user's side. If no version ID was found at the specified path inside the user file, or it is not valid, the first version ID of the pattern is assigned to it.

After a successful update, the version ID of the user file is automatically updated to the ID of the defaults (against which we updated).

Malforming the version ID in the user file might still lead to potentially destructive actions and loss of content.

This only applies if using relocations and keep paths and the file with the malformed version was of version ID newer than the first one.

You should always instruct (using a comment) users that the ID should never be changed.

Please note that once you chose a path, at which the IDs will be present, you cannot change it.

Last updated