Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use global variables? #111

Open
1507057ff opened this issue Nov 23, 2024 · 12 comments
Open

How to use global variables? #111

1507057ff opened this issue Nov 23, 2024 · 12 comments

Comments

@1507057ff
Copy link

How to define global variables in Redux, similar to the syntax of SannyBuilder, so that any script can share this global variable
image

@nathan130200
Copy link

nathan130200 commented Nov 24, 2024

Try declare some var in global namespace, idk if Javascript VMs share same global object in redux.

Nevermind, tested here they don't share same global object.

16:18:59 [INFO] ["script2"] "set script2 var"
16:19:01 [INFO] ["script1"] "get script2 var: undefined"

script1.ts

wait(1500);
log('get script2 var: ' + globalThis['script2_var'])

script2.ts

globalThis['script2_var'] = 1234;
log('set script2 var')
wait(1500)

Maybe an work-around using ScriptObject extended vars?

Also theres dispatchEvent and addEventListener works for communication between scripts:

script2: dispatchEvent("Script2:OnLoad");
script1:

addEventListener("Script2:OnLoad", () => {
   log("script1: received script2 load callback!")
});

@1507057ff
Copy link
Author

1507057ff commented Nov 26, 2024

@nathan130200

Thank you, but the variable scope of addEventListener differs from the ECMAScript.

eventListener.ts

interface OnPedCreateEvent {
  name: string;
  data: { address: int };
}

const peds: number[] = [];

addEventListener('OnPedCreate', ({ data }: OnPedCreateEvent) => peds.push(data.address));

addEventListener('logPeds', () => {
  log(`logPeds: ${peds.join()}`);
});

export const getPeds = () => {
  log(`getPeds: ${peds.join()}`);
  return peds;
};

script1.ts

import { getPeds} from 'eventListener.ts';

getPeds();
dispatchEvent('logPeds');
21:25:03 [INFO] ["script1"] "getPeds: "
21:25:03 [INFO] ["eventLi"] "logPeds: 399654312,399657816,399664824,399666576,399668328,399657816,399640296,399636792,399642048,399650808,399652560,399654312,399650808,399656064,399657816,399661320"

In the getPeds() function, peds is the initial value.

This means that when using peds, the code can only be written in the addEventListener callback function of eventListener.ts.

I tried putting the peds in other files but it didn't work.

tool.ts

export class PedSingle {
  static instance: PedSingle;

  private peds: Char[] = [];

  constructor() {
    if (!PedSingle.instance) {
      PedSingle.instance = this;
    }
    return PedSingle.instance;
  }

  getPeds() {
    try {
      return this.peds.filter((item) => !Char.IsDead(item as any));
    } catch (error) {
      log(`PedSingle getPeds error: ${error}`);
      return [];
    }
  }

  addPed(char: Char) {
    try {
      this.peds = this.getPeds();
      this.peds.push(char);
    } catch (error) {
      log(`PedSingle addPed error: ${error}`);
    }
  }
}

export const PedSingleInstance = new PedSingle();

eventListener.ts

import { PedSingle } from './.config/tools.ts';

addEventListener('OnPedCreate', (event: OnPedCreateEvent) =>
  PedSingle.instance.addPed(Memory.GetPedRef(event.data.address))
);

addEventListener('logPeds', () => {
  log(
    `eventListener-logPeds: ${PedSingle.instance
      .getPeds()
      .map((item) => item.valueOf())
      .join()}`
  );
});

script1.ts

import { PedSingle } from './.config/tools.ts';

      dispatchEvent('logPeds');
      log(
        `getPeds: ${PedSingle.instance
          .getPeds()
          .map((item) => item.valueOf())
          .join()}`
      );

logPeds.ts

import { PedSingle } from './.config/tools.ts';

addEventListener('logPeds', () => {
  log(
    `logPeds: ${PedSingle.instance
      .getPeds()
      .map((item) => item.valueOf())
      .join()}`
  );
});
21:43:02 [INFO] ["script1"] "getPeds: "
21:43:02 [INFO] ["logPeds"] "logPeds: "
21:43:02 [INFO] ["eventLi"] "eventListener-logPeds: 1820,2076,1560,2588"

Only in the callback function of addEventListener in addEventListener. ts, the printed value is correct

@x87
Copy link
Contributor

x87 commented Nov 26, 2024

import { getPeds} from 'eventListener.ts';

getPeds();
dispatchEvent('logPeds');

in this example getPeds gets called before any async callback (including event listeners) which is why peds is still empty. logPeds is an async event and gets called one frame later. Try the following code to delay getPeds:

import { getPeds} from 'eventListener.ts';

setTimeout(getPeds, 0);
dispatchEvent('logPeds');

@1507057ff
Copy link
Author

1507057ff commented Nov 27, 2024

But in the second example PedSingle class
image
logPeds in event listeners, peds is empty.

But in the eventListener.ts script, the value is correct.
image

Does this seem like a bug?

@x87
Copy link
Contributor

x87 commented Nov 27, 2024

Add log to OnPedCreate and see what gets called first: OnPedCreate callback or your event

@1507057ff
Copy link
Author

script1.ts

import { KeyCode } from './.config/vc.enums.ts';

while (true) {
  wait(0);

  if (Pad.IsKeyDown(KeyCode.Num2)) {
    log('dispatchEvent:logPeds');
    dispatchEvent('logPeds');
  }
}

eventListener.ts

addEventListener('OnPedCreate', (event: OnPedCreateEvent) => {
  const char = Memory.GetPedRef(event.data.address);
  log(`new PedCreate: ${char.valueOf()}`);
  PedSingle.instance.addPed(char);
});

addEventListener('logPeds', () => {
  log(
    `logPeds: ${PedSingle.instance
      .getPeds()
      .map((item) => item.valueOf())
      .join()}`
  );
});

logPeds.ts

import { PedSingle } from './.config/tools.ts';

addEventListener('logPeds', () => {
  log(
    `logPeds: ${PedSingle.instance
      .getPeds()
      .map((item) => item.valueOf())
      .join()}`
  );
});
22:25:45 [INFO] ["eventLi"] "new PedCreate: 1795"
22:25:47 [INFO] ["eventLi"] "new PedCreate: 1285"
22:25:47 [INFO] ["eventLi"] "new PedCreate: 1542"
22:25:48 [INFO] ["eventLi"] "new PedCreate: 1796"
22:25:48 [INFO] ["eventLi"] "new PedCreate: 2051"
22:25:49 [INFO] ["script1"] "dispatchEvent:logPeds"
22:25:49 [INFO] ["eventLi"] "logPeds: 1285,1542,1796,2051"
22:25:49 [INFO] ["logPeds"] "logPeds: "

@x87
Copy link
Contributor

x87 commented Nov 27, 2024

eventListener.ts and logPeds.ts are two separate scripts, they don't share memory. Each of them has its own copy of PedSingle. PedSingle peds only gets populated inside eventListener.ts because of addEventListener('OnPedCreate' here. It does not affect logPeds.ts

@x87
Copy link
Contributor

x87 commented Nov 27, 2024

in other words, each script owns it's own copy of runtime. you can share memory by allocating a memory chunk in one script and passing a pointer to it using CLEO.runScript

But I can hardly imagine when this can be needed unless you're cooking something really complicated. You can just combine all files in one big script. You can run different subscripts using this technique

@1507057ff
Copy link
Author

1507057ff commented Nov 27, 2024

Oh, thank you. I'll try again. Can you support simpler sharing of global variables?

At first, I used World.GetRandomCharInSphereNoSaveRecursive retrieves pedestrians around the player, but it consumes a lot of performance.

So I am considering using the OnPedCreate event to implement it, and it can be read normally in any script, when needed.

  for (let i = -xyRadius; i < xyRadius; i += radius) {
    for (let j = -xyRadius; j < xyRadius; j += radius) {
      for (let k = -zRadius; k < zRadius; k++) {
        chars.push(World.GetRandomCharInSphereNoSaveRecursive(x + i, y + j, z + k, radius, false, true));
      }
    }
  }

@x87
Copy link
Contributor

x87 commented Nov 27, 2024

There are multiple ways of sharing data across scripts already.

  1. using shared allocated or static memory. Allocate memory and pass a pointer to it to another script, or use some specific memory region unused by the game
  2. Using main.scm global variables https://github.com/x87/scm.ts?tab=readme-ov-file#scm Can potentially conflict with game script
  3. Events to send serializable (numbers and strings) data across scripts.

I don't plan on adding more ways, especially for global state because it's always more trouble than help. If you need a shared data, put it all in one script and think how you can cache some costly operations.

@1507057ff
Copy link
Author

Okay, thank you. I don't know much about CLEO, I am familiar with JS syntax, so I am trying to write some simple functions myself. I think this phenomenon is quite strange, so I brought up this issue.

I will try your suggestion.

@nathan130200
Copy link

nathan130200 commented Nov 27, 2024

There's another (more complex and weird) way to do this, its implementing basic RPC-like structure around addEventListener and dispatchEvent like multiplayer games does. But its simple just use memory allocation or CLEO.runScript function (or maybe some C++ plugin that communicate with CLEO redux and use custom commands?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants