Skip to content

Commit

Permalink
mitigates paper fields lagging out a lot (shiptest-ss13#2738)
Browse files Browse the repository at this point in the history
<!-- Write **BELOW** The Headers and **ABOVE** The comments else it may
not be viewable. -->
<!-- You can view Contributing.MD for a detailed description of the pull
request process. -->

## About The Pull Request

Finishes porting tgstation/tgstation#73628

<!-- Describe The Pull Request. Please be sure every change is
documented or this can delay review and even discourage maintainers from
merging your PR! -->

## Why It's Good For The Game

paperlag bad

<!-- Please add a short description of why you think these changes would
benefit the game. If you can't justify it in words, it might not be
worth adding. -->

## Changelog

:cl: Timberpoes
fix: Papercode has been significantly improved and trivially filled
paper forms should no longer lag or crash players' game clients.
/:cl:

<!-- Both :cl:'s are required for the changelog to work! You can put
your name to the right of the first :cl: if you want to overwrite your
GitHub username as author ingame. -->
<!-- You can use multiple of the same prefix (they're only used for the
icon ingame) and delete the unneeded ones. Despite some of the tags,
changelogs should generally represent how a player might be affected by
the changes rather than a summary of the PR's contents. -->
  • Loading branch information
meemofcourse authored and MysticalFaceLesS committed Mar 5, 2024
1 parent cac06aa commit bde6eda
Showing 1 changed file with 101 additions and 11 deletions.
112 changes: 101 additions & 11 deletions tgui/packages/tgui/interfaces/PaperSheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -430,10 +430,83 @@ export class PreviewView extends Component<PreviewViewProps> {
// Array containing cache of HTMLInputElements that are enabled.
enabledInputFieldCache: { [key: string]: HTMLInputElement } = {};

// State checking variables. Used to determine whether or not to use cache.
lastReadOnly: boolean = true;
lastDMInputCount: number = 0;
lastFieldCount: number = 0;
lastFieldInputCount: number = 0;

// Cache variables for fully parsed text. Workaround for marked.js not being
// super fast on the BYOND/IE js engine.
parsedDMCache: string = '';
parsedTextBoxCache: string = '';

constructor(props, context) {
super(props, context);
this.configureMarked();
}

configureMarked = (): void => {
// This is an extension for marked defining a complete custom tokenizer.
// This tokenizer should run before the the non-custom ones, and gives us
// the ability to handle [_____] fields before the em/strong tokenizers
// mangle them, since underscores are used for italic/bold.
// This massively improves the order of operations, allowing us to run
// marked, THEN sanitise the output (much safer) and finally insert fields
// manually afterwards.
const inputField = {
name: 'inputField',
level: 'inline',

start(src) {
return src.match(/\[/)?.index;
},

tokenizer(src: string) {
const rule = /^\[_+\]/;
const match = src.match(rule);
if (match) {
const token = {
type: 'inputField',
raw: match[0],
};
return token;
}
},

renderer(token) {
return `${token.raw}`;
},
};

// Override function, any links and images should
// kill any other marked tokens we don't want here
const walkTokens = (token) => {
switch (token.type) {
case 'url':
case 'autolink':
case 'reflink':
case 'link':
case 'image':
token.type = 'text';
// Once asset system is up change to some default image
// or rewrite for icon images
token.href = '';
break;
}
};

marked.use({
extensions: [inputField],
breaks: true,
gfm: true,
smartypants: true,
walkTokens: walkTokens,
// Once assets are fixed might need to change this for them
baseUrl: 'thisshouldbreakhttp',
});
};

// Extracts the paper field "counter" from a full ID.
getHeaderID = (header: string): string => {
return header.replace('paperfield_', '');
Expand All @@ -457,6 +530,7 @@ export class PreviewView extends Component<PreviewViewProps> {

// Skip text area input.
if (input.nodeName !== 'INPUT') {
this.parsedTextBoxCache = '';
return;
}

Expand Down Expand Up @@ -494,6 +568,7 @@ export class PreviewView extends Component<PreviewViewProps> {
createPreviewFromDM = (): { text: string; newFieldCount: number } => {
const { data } = useBackend<PaperContext>(this.context);
const {
raw_field_input,
raw_text_input,
default_pen_font,
default_pen_color,
Expand All @@ -506,6 +581,19 @@ export class PreviewView extends Component<PreviewViewProps> {

const readOnly = !canEdit(held_item_details);

// If readonly is the same (input field writiability state hasn't changed)
// And the input stats are the same (no new text inputs since last time)
// Then use any cached values.
if (
this.lastReadOnly === readOnly &&
this.lastDMInputCount === raw_text_input?.length &&
this.lastFieldInputCount === raw_field_input?.length
) {
return { text: this.parsedDMCache, newFieldCount: this.lastFieldCount };
}

this.lastReadOnly = readOnly;

raw_text_input?.forEach((value) => {
let rawText = value.raw_text.trim();
if (!rawText.length) {
Expand Down Expand Up @@ -533,6 +621,11 @@ export class PreviewView extends Component<PreviewViewProps> {
fieldCount = processingOutput.nextCounter;
});

this.lastDMInputCount = raw_text_input?.length || 0;
this.lastFieldInputCount = raw_field_input?.length || 0;
this.lastFieldCount = fieldCount;
this.parsedDMCache = output;

return { text: output, newFieldCount: fieldCount };
};

Expand All @@ -548,6 +641,11 @@ export class PreviewView extends Component<PreviewViewProps> {
} = data;
const { textArea } = this.props;

// Use the cache if one exists.
if (this.parsedTextBoxCache) {
return this.parsedTextBoxCache;
}

const readOnly = true;

const fontColor = held_item_details?.color || default_pen_color;
Expand All @@ -564,6 +662,8 @@ export class PreviewView extends Component<PreviewViewProps> {
readOnly
);

this.parsedTextBoxCache = processingOutput.text;

return processingOutput.text;
};

Expand Down Expand Up @@ -630,17 +730,7 @@ export class PreviewView extends Component<PreviewViewProps> {
},
};

// marked.use({ tokenizer });
marked.use({ extensions: [inputField] });

return marked.parse(rawText, {
breaks: true,
smartypants: true,
smartLists: true,
walkTokens,
// Once assets are fixed might need to change this for them
baseUrl: 'thisshouldbreakhttp',
});
return marked.parse(rawText);
};

// Fully formats, sanitises and parses the provided raw text and wraps it
Expand Down

0 comments on commit bde6eda

Please sign in to comment.