philippj / SteamworksPy

A working Python API system for Valve's Steamworks.
MIT License
212 stars 39 forks source link

SubscribeItem callback #58

Closed rimpy-custom closed 2 years ago

rimpy-custom commented 3 years ago

I am trying to use steamworks.Workshop.SubscribeItem([code], callback) where callback is a function that prints a string but it never executes. I want to make sure that item actually was subscribed, but not sure how to do that properly. May I ask for a hand?

import os
import sys
if sys.version_info >= (3, 8):
  os.add_dll_directory(os.getcwd()) # Required since Python 3.8
from steamworks import STEAMWORKS # Import main STEAMWORKS class

def call_me():
    print("test")
    return print("test2")

steamworks = STEAMWORKS()
steamworks.initialize()
steamworks.Workshop.SubscribeItem([steamId], call_me)
steamworks.run_callbacks()

May I ask for an example how to use callback properly?

Gramps commented 3 years ago

Hey there! The function run_callbacks needs to be in a loop where it is called every tick or so. This will trigger certain other functions to fire when the callback comes through. Beyond that, I have never used the callbacks in Python. I hope that helps.

philippj commented 3 years ago

Hey there! The function run_callbacks needs to be in a loop where it is called every tick or so. This will trigger certain other functions to fire when the callback comes through. Beyond that, I have never used the callbacks in Python. I hope that helps.

This is the way to go. Use the run_callbacks method either in a main loop or a different thread depending on use case.

rimpy-custom commented 3 years ago

Have tried that, but still no luck:

import os
import sys

if sys.version_info >= (3, 8):
  os.add_dll_directory(os.getcwd()) # Required since Python 3.8

from steamworks import STEAMWORKS # Import main STEAMWORKS class

def checkCallbacks(obj):
  while True:
    obj.run_callbacks()

def call_me():
    print("test")
    return print("test2")

if __name__ == '__main__':

  steamworks = STEAMWORKS()
  steamworks.initialize()

  from threading import Thread
  th = Thread(target=checkCallbacks, args=(steamworks,))
  th.start()

  steamworks.Workshop.SubscribeItem([steamId], call_me)
Gramps commented 3 years ago

Where is the SteamID you're passing to SubscribeItem being set from? I'm also rusty on my Python; that being said, is there a main loop that is still running?

rimpy-custom commented 3 years ago

I just run script from command line. Not sure but it is a main loop, probably. Because command line did not close after script was run. Afaik, threads in Python runs concurrently and not parallel. Not sure if that may be a problem in current situation.

I set [steamid] manually as integer, for example 2336610918. It is an item from Rimworld game workshop.

Gramps commented 3 years ago

Does your print display?

philippj commented 3 years ago

@rimpy-custom I could confirm your issue. However, I am unsure about what exactly is causing this.

Calling steamworks.Workshop.GetNumSubscribedItems() makes the entire interface work. I have added the call in the main module init so that it will work consistently.

rimpy-custom commented 3 years ago

Have tried but still an issue for me. No text printed. May it be python interpreter? I am on latest 3.9.0. @Gramps yeah, it just prints "test" when function called. Since we run thread, it will print it inthe same command line window.

rimpy-custom commented 3 years ago

@philippj made some tests. I was able to get callback. Looks like the issue is in here https://github.com/philippj/SteamworksPy/blob/98272dd1b6b41f25431bb1f30f5ecc2775ffe248/steamworks/interfaces/workshop.py#L124

It checks self._RemoteStorageSubscribePublishedFileResult, but it always False. Probably, because it is async and result is not ready yet. So I have removed self._RemoteStorageSubscribePublishedFileResult from a condition and callback was called. Not sure, maybe I broke smth also.

P. S. I made tests on your latest master branch with latest changes you have done. Also I have added override_callback=True to method call and call_me needs an argument. So the full example is that:

import os
import sys

if sys.version_info >= (3, 8):
  os.add_dll_directory(os.getcwd()) # Required since Python 3.8

from steamworks import STEAMWORKS # Import main STEAMWORKS class

def checkCallbacks(obj):
  obj.run_forever(0)

def call_me(result):
  print(result)
  print("It is a callback!")
  return print("It is a callback!")

if __name__ == '__main__':

  steamworks = STEAMWORKS()
  steamworks.initialize()

  from threading import Thread
  th = Thread(target=checkCallbacks, args=(steamworks,))
  th.start()

  steamworks.Workshop.SubscribeItem(2336610918, call_me, override_callback=True)
rimpy-custom commented 3 years ago

Also I have found that run_callbacks() may be called later, not in thread. As far as I find out, it should be run after async result arrived. So smth like time.sleep(5) will work, if you know that answer will received in 5 seconds. But it is still better to run_forever() in thread and be called in intervals, because you do not know how long that may take to receive a result.

philippj commented 3 years ago

@rimpy-custom See this: https://gist.github.com/philippj/abf45e3e8e5fbe521125ae1cfe4de7d4

What I could not make out from your snippet is whether or not you have a main loop. You can also call the run_callbacks from your main loop, but that depends on your overall application structure. The threading and thus "async" calling of the run_callbacks is not required. run_forever works also just fine as it basically only calls run_callbacks with a delay. You should never use a base_interval of 0 or generally not sleep between run_callbacks.

Removing self._RemoteStorageSubscribePublishedFileResult does not actually change anything. Yes it will be None/False as you have not supplied a callback via SetItemSubscribedCallback. Thats what the second (and third) parameter are for in SubscribeItem.

So smth like time.sleep(5) will work, if you know that answer will received in 5 seconds.

SubscribeItem will not only subscribe the current user to the item but will also initiate some internal stuff. You will only receive the ItemSubscribed event after that has been finished. Using my test script, I receive the events almost instantaneous.

rimpy-custom commented 3 years ago

If I run script as is, then I do not have mainloop, am I right? Then I do not have any. That is the cause, probably. Basicly I spawn a process, where I instantiate and initialize Steamworks object, spawn a new thread in it where call run_callbacks, then run subscribe methods in there and wait for callbacks. When job done I kill that Process and return to main program. I need to do that because I do not want to see Steam Overlay in my main PyQt window. Afaik, there is no way to disable steam overlay or shutdown Steam API after it was initialized.

Thank you for your project, time and example. I will try to build smth with that info.

P. S. It is a third-party mod manager and not game itself. So I need sub/unsub only functionality, without overlays or smth.

philippj commented 3 years ago

If you do not have a main loop and just run the script you supplied as is, your script will exit and the callback will never be called. You need to either join the thread or put in a main loop that keeps the process alive.

rimpy-custom commented 3 years ago

@philippj I see that in the example you call SetItemSubscribedCallback and then SubscribeItem. So _RemoteStorageSubscribePublishedFileResult will not be None. But you may also call SubscribeItem with arguments and set callback function in it SubscribeItem(self, published_file_id: int, callback: object = None, override_callback: bool = False). If you do so and make override_callback=True, then _RemoteStorageSubscribePublishedFileResult will always be False and SetItemSubscribedCallback never called. Because we check it before running SetItemSubscribedCallback, which changes it from None Am I right on that? https://github.com/philippj/SteamworksPy/blob/98272dd1b6b41f25431bb1f30f5ecc2775ffe248/steamworks/interfaces/workshop.py#L115

rimpy-custom commented 3 years ago

UPDATE: tested both. Yes, you either need to run SetItemSubscribedCallback and then SubscribeItem for callback to work or modify code to run SubscribeItem if you specify callback in it.

philippj commented 2 years ago

Is this still relevant?

rimpy-custom commented 2 years ago

No, thank you