-
Notifications
You must be signed in to change notification settings - Fork 126
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
Plans for v1.0.0 #314
Comments
Hello, FlyGoat! Congratulations on reaching the 5-year milestone for our project! I’m excited about the upcoming release of v1.0.0 and the potential improvements you've proposed. Below, I've structured my feedback on each area, along with some additional suggestions that could enhance functionality further. Feedback on Proposed Updates
Suggested Enhancements to
|
was going to open an issue but then noticed this you can take ideas or pr if you want i updated main for a quick test before implementing in app, you can see how the new api looks like main// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2018-2019 Jiaxun Yang <[email protected]> */
/* Ryzen NB SMU Service Request Tool */
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "lib/ryzenadj.h"
#include "argparse.h"
#define STRINGIFY2(X) #X
#define STRINGIFY(X) STRINGIFY2(X)
static const char *const usage[] = {
"ryzenadj [options]",
NULL,
};
static const char *family_name(const RYZEN_FAMILY fam)
{
switch (fam)
{
case FAM_RAVEN: return "Raven";
case FAM_PICASSO: return "Picasso";
case FAM_RENOIR: return "Renoir";
case FAM_CEZANNE: return "Cezanne";
case FAM_DALI: return "Dali";
case FAM_LUCIENNE: return "Lucienne";
case FAM_VANGOGH: return "Vangogh";
case FAM_REMBRANDT: return "Rembrandt";
case FAM_PHOENIX: return "Phoenix Point";
case FAM_HAWKPOINT: return "Hawk Point";
case FAM_STRIXPOINT: return "Strix Point";
default:
break;
}
return "Unknown";
}
static void show_info_header()
{
printf("CPU Family: %s\n", family_name(ryzenadj_get_cpu_family()));
printf("SMU BIOS Interface Version: %d\n", ryzenadj_get_bios_if_ver());
printf("Version: v" STRINGIFY(RYZENADJ_REVISION_VER) "." STRINGIFY(RYZENADJ_MAJOR_VER) "." STRINGIFY(RYZENADJ_MINIOR_VER) " \n");
}
static void show_info_table()
{
printf("PM Table Version: %x\n", ryzenadj_get_table_ver());
//get refresh table after adjust
const ADJ_ERROR err = ryzenadj_refresh_table();
if(err != ADJ_OK){
printf("Unable to refresh power metric table: %d\n", err);
return;
}
//print table in github markdown
printf("| Name | Value | Parameter |\n");
printf("|---------------------|-----------|--------------------|\n");
char tableFormat[] = "| %-19s | %9.3lf | %-18s |\n";
printf(tableFormat, "STAPM LIMIT", ryzenadj_get(ADJ_OPT_STAPM_LIMIT, NULL), "stapm-limit");
printf(tableFormat, "STAPM VALUE", ryzenadj_get_value(ADJ_OPT_STAPM_LIMIT, NULL), "");
printf(tableFormat, "PPT LIMIT FAST", ryzenadj_get(ADJ_OPT_FAST_LIMIT, NULL), "fast-limit");
printf(tableFormat, "PPT VALUE FAST", ryzenadj_get_value(ADJ_OPT_FAST_LIMIT, NULL), "");
printf(tableFormat, "PPT LIMIT SLOW", ryzenadj_get(ADJ_OPT_SLOW_LIMIT, NULL), "slow-limit");
printf(tableFormat, "PPT VALUE SLOW", ryzenadj_get_value(ADJ_OPT_SLOW_LIMIT, NULL), "");
printf(tableFormat, "StapmTimeConst", ryzenadj_get(ADJ_OPT_STAPM_TIME, NULL), "stapm-time");
printf(tableFormat, "SlowPPTTimeConst", ryzenadj_get(ADJ_OPT_SLOW_TIME, NULL), "slow-time");
printf(tableFormat, "PPT LIMIT APU", ryzenadj_get(ADJ_OPT_APU_SLOW_LIMIT, NULL), "apu-slow-limit");
printf(tableFormat, "PPT VALUE APU", ryzenadj_get_value(ADJ_OPT_APU_SLOW_LIMIT, NULL), "");
printf(tableFormat, "TDC LIMIT VDD", ryzenadj_get(ADJ_OPT_VRM_CURRENT, NULL), "vrm-current");
printf(tableFormat, "TDC VALUE VDD", ryzenadj_get_value(ADJ_OPT_VRM_CURRENT, NULL), "");
printf(tableFormat, "TDC LIMIT SOC", ryzenadj_get(ADJ_OPT_VRMSOC_CURRENT, NULL), "vrmsoc-current");
printf(tableFormat, "TDC VALUE SOC", ryzenadj_get_value(ADJ_OPT_VRMSOC_CURRENT, NULL), "");
printf(tableFormat, "EDC LIMIT VDD", ryzenadj_get(ADJ_OPT_VRMMAX_CURRENT, NULL), "vrmmax-current");
printf(tableFormat, "EDC VALUE VDD", ryzenadj_get_value(ADJ_OPT_VRMMAX_CURRENT, NULL), "");
printf(tableFormat, "EDC LIMIT SOC", ryzenadj_get(ADJ_OPT_VRMSOCMAX_CURRENT, NULL), "vrmsocmax-current");
printf(tableFormat, "EDC VALUE SOC", ryzenadj_get_value(ADJ_OPT_VRMSOCMAX_CURRENT, NULL), "");
printf(tableFormat, "THM LIMIT CORE", ryzenadj_get(ADJ_OPT_TCTL_TEMP, NULL), "tctl-temp");
printf(tableFormat, "THM VALUE CORE", ryzenadj_get_value(ADJ_OPT_TCTL_TEMP, NULL), "");
printf(tableFormat, "STT LIMIT APU", ryzenadj_get(ADJ_OPT_APU_SKIN_TEMP_LIMIT, NULL), "apu-skin-temp");
printf(tableFormat, "STT VALUE APU", ryzenadj_get_value(ADJ_OPT_APU_SKIN_TEMP_LIMIT, NULL), "");
printf(tableFormat, "STT LIMIT dGPU", ryzenadj_get(ADJ_OPT_DGPU_SKIN_TEMP_LIMIT, NULL), "dgpu-skin-temp");
printf(tableFormat, "STT VALUE dGPU", ryzenadj_get_value(ADJ_OPT_DGPU_SKIN_TEMP_LIMIT, NULL), "");
printf(tableFormat, "CCLK Boost SETPOINT", ryzenadj_get_value(ADJ_OPT_CCLK_SETPOINT, NULL), "power-saving /");
printf(tableFormat, "CCLK BUSY VALUE", ryzenadj_get_value(ADJ_OPT_CCLK_BUSY, NULL), "max-performance");
}
static void show_table_dump(const int any_adjust_applied)
{
size_t index, table_size;
uint32_t *table_data_copy;
float *current_table_values, *old_table_values;
printf("PM Table Dump of Version: %x\n", ryzenadj_get_table_ver());
table_size = ryzenadj_get_table_size();
current_table_values = ryzenadj_get_table_values();
table_data_copy = malloc(table_size);
memcpy(table_data_copy, current_table_values, table_size);
if(any_adjust_applied)
{
//copy old values before refresh
old_table_values = malloc(table_size);
memcpy(old_table_values, table_data_copy, table_size);
const ADJ_ERROR err = ryzenadj_refresh_table();
if(err != ADJ_OK)
printf("Unable to refresh power metric table: %d\n", err);
//print table in github markdown
printf("| Offset | Data | Value | After Adjust |\n");
printf("|--------|------------|-----------|--------------|\n");
char tableFormat[] = "| 0x%04X | 0x%08X | %9.3lf | %12.3lf |\n";
for(index = 0; index < table_size / 4; index++)
printf(tableFormat, index * 4, table_data_copy[index], old_table_values[index], current_table_values[index]);
free(old_table_values);
}
else
{
//print table in github markdown
printf("| Offset | Data | Value |\n");
printf("|--------|------------|-----------|\n");
char tableFormat[] = "| 0x%04X | 0x%08X | %9.3lf |\n";
for(index = 0; index < table_size / 4; index++)
printf(tableFormat, index * 4, table_data_copy[index], current_table_values[index]);
}
free(table_data_copy);
//don't free current_table_values because this would deinitialize our table
}
static void setOpt(const ADJ_OPT opt, const uint32_t val, ADJ_ERROR *err) {
if (val == -1)
return;
*err = ryzenadj_set(opt, val);
}
static void setOptEnable(const ADJ_OPT opt, const uint32_t val, ADJ_ERROR *err) {
if (!val)
return;
*err = ryzenadj_set(opt, 0);
}
int main(int argc, const char **argv)
{
int info = 0, dump_table = 0;
int power_saving = 0, max_performance = 0, enable_oc = 0x0, disable_oc = 0x0;
//init unsigned types with max value because we treat max value as unset
uint32_t stapm_limit = -1, fast_limit = -1, slow_limit = -1, slow_time = -1, stapm_time = -1, tctl_temp = -1;
uint32_t vrm_current = -1, vrmsoc_current = -1, vrmmax_current = -1, vrmsocmax_current = -1, psi0_current = -1, psi0soc_current = -1;
uint32_t vrmgfx_current = -1, vrmcvip_current = -1, vrmgfxmax_current = -1, psi3cpu_current = -1, psi3gfx_current = -1;
uint32_t max_socclk_freq = -1, min_socclk_freq = -1, max_fclk_freq = -1, min_fclk_freq = -1, max_vcn = -1, min_vcn = -1, max_lclk = -1, min_lclk = -1;
uint32_t max_gfxclk_freq = -1, min_gfxclk_freq = -1, prochot_deassertion_ramp = -1, apu_skin_temp_limit = -1, dgpu_skin_temp_limit = -1, apu_slow_limit = -1;
uint32_t skin_temp_power_limit = -1;
uint32_t gfx_clk = -1, oc_clk = -1, oc_volt = -1, coall = -1, coper = -1, cogfx = -1;
ADJ_ERROR adjerr;
//create structure for parseing
struct argparse_option options[] = {
OPT_HELP(),
OPT_GROUP("Options"),
OPT_BOOLEAN('i', "info", &info, "Show information and most important power metrics after adjustment"),
OPT_BOOLEAN('\0', "dump-table", &dump_table, "Show whole power metric table before and after adjustment"),
OPT_GROUP("Settings"),
OPT_U32('a', "stapm-limit", &stapm_limit, "Sustained Power Limit - STAPM LIMIT (mW)"),
OPT_U32('b', "fast-limit", &fast_limit, "Actual Power Limit - PPT LIMIT FAST (mW)"),
OPT_U32('c', "slow-limit", &slow_limit, "Average Power Limit - PPT LIMIT SLOW (mW)"),
OPT_U32('d', "slow-time", &slow_time, "Slow PPT Constant Time (s)"),
OPT_U32('e', "stapm-time", &stapm_time, "STAPM constant time (s)"),
OPT_U32('f', "tctl-temp", &tctl_temp, "Tctl Temperature Limit (degree C)"),
OPT_U32('g', "vrm-current", &vrm_current, "VRM Current Limit - TDC LIMIT VDD (mA)"),
OPT_U32('j', "vrmsoc-current", &vrmsoc_current, "VRM SoC Current Limit - TDC LIMIT SoC (mA)"),
OPT_U32('\0', "vrmgfx-current", &vrmgfx_current, "VRM GFX Current Limit - TDC LIMIT GFX (mA)"),
OPT_U32('\0', "vrmcvip-current", &vrmcvip_current, "VRM CVIP Current Limit - TDC LIMIT CVIP (mA)"),
OPT_U32('k', "vrmmax-current", &vrmmax_current, "VRM Maximum Current Limit - EDC LIMIT VDD (mA)"),
OPT_U32('l', "vrmsocmax-current", &vrmsocmax_current, "VRM SoC Maximum Current Limit - EDC LIMIT SoC (mA)"),
OPT_U32('\0', "vrmgfxmax_current", &vrmgfxmax_current, "VRM GFX Maximum Current Limit - EDC LIMIT GFX (mA)"),
OPT_U32('m', "psi0-current", &psi0_current, "PSI0 VDD Current Limit (mA)"),
OPT_U32('\0', "psi3cpu_current", &psi3cpu_current, "PSI3 CPU Current Limit (mA)"),
OPT_U32('n', "psi0soc-current", &psi0soc_current, "PSI0 SoC Current Limit (mA)"),
OPT_U32('\0', "psi3gfx_current", &psi3gfx_current, "PSI3 GFX Current Limit (mA)"),
OPT_U32('o', "max-socclk-frequency", &max_socclk_freq, "Maximum SoC Clock Frequency (MHz)"),
OPT_U32('p', "min-socclk-frequency", &min_socclk_freq, "Minimum SoC Clock Frequency (MHz)"),
OPT_U32('q', "max-fclk-frequency", &max_fclk_freq, "Maximum Transmission (CPU-GPU) Frequency (MHz)"),
OPT_U32('r', "min-fclk-frequency", &min_fclk_freq, "Minimum Transmission (CPU-GPU) Frequency (MHz)"),
OPT_U32('s', "max-vcn", &max_vcn, "Maximum Video Core Next (VCE - Video Coding Engine) (MHz)"),
OPT_U32('t', "min-vcn", &min_vcn, "Minimum Video Core Next (VCE - Video Coding Engine) (MHz)"),
OPT_U32('u', "max-lclk", &max_lclk, "Maximum Data Launch Clock (MHz)"),
OPT_U32('v', "min-lclk", &min_lclk, "Minimum Data Launch Clock (MHz)"),
OPT_U32('w', "max-gfxclk", &max_gfxclk_freq, "Maximum GFX Clock (MHz)"),
OPT_U32('x', "min-gfxclk", &min_gfxclk_freq, "Minimum GFX Clock (MHz)"),
OPT_U32('y', "prochot-deassertion-ramp", &prochot_deassertion_ramp, "Ramp Time After Prochot is Deasserted: limit power based on value, higher values does apply tighter limits after prochot is over"),
OPT_U32('\0', "apu-skin-temp", &apu_skin_temp_limit, "APU Skin Temperature Limit - STT LIMIT APU (degree C)"),
OPT_U32('\0', "dgpu-skin-temp", &dgpu_skin_temp_limit, "dGPU Skin Temperature Limit - STT LIMIT dGPU (degree C)"),
OPT_U32('\0', "apu-slow-limit", &apu_slow_limit, "APU PPT Slow Power limit for A+A dGPU platform - PPT LIMIT APU (mW)"),
OPT_U32('\0', "skin-temp-limit", &skin_temp_power_limit, "Skin Temperature Power Limit (mW)"),
OPT_U32('\0', "gfx-clk", &gfx_clk, "Forced Clock Speed MHz (Renoir Only)"),
OPT_U32('\0', "oc-clk", &oc_clk, "Forced Core Clock Speed MHz (Renoir and up Only)"),
OPT_U32('\0', "oc-volt", &oc_volt, "Forced Core VID: Must follow this calcuation (1.55 - [VID you want to set e.g. 1.25 for 1.25v]) / 0.00625 (Renoir and up Only)"),
OPT_BOOLEAN('\0', "enable-oc", &enable_oc, "Enable OC (Renoir and up Only)"),
OPT_BOOLEAN('\0', "disable-oc", &disable_oc, "Disable OC (Renoir and up Only)"),
OPT_U32('\0', "set-coall", &coall, "All core Curve Optimiser"),
OPT_U32('\0', "set-coper", &coper, "Per core Curve Optimiser"),
OPT_U32('\0', "set-cogfx", &cogfx, "iGPU Curve Optimiser"),
OPT_BOOLEAN('\0', "power-saving", &power_saving, "Hidden options to improve power efficiency (is set when AC unplugged): behavior depends on CPU generation, Device and Manufacture"),
OPT_BOOLEAN('\0', "max-performance", &max_performance, "Hidden options to improve performance (is set when AC plugged in): behavior depends on CPU generation, Device and Manufacture"),
OPT_GROUP("P-State Functions"),
OPT_END(),
};
struct argparse argparse;
argparse_init(&argparse, options, usage, ARGPARSE_NON_OPTION_IS_INVALID);
argparse_describe(&argparse, "\n Ryzen Power Management adjust tool.", "\nWARNING: Use at your own risk!\nBy Jiaxun Yang <[email protected]>, Under LGPL.\nVersion: v" STRINGIFY(RYZENADJ_REVISION_VER) "." STRINGIFY(RYZENADJ_MAJOR_VER) "." STRINGIFY(RYZENADJ_MINIOR_VER));
argc = argparse_parse(&argparse, argc, argv);
//init RyzenAdj and validate that it was able to
adjerr = ryzenadj_init();
if(adjerr != ADJ_OK){
printf("Unable to init ryzenadj\n");
return -1;
}
//shows info header before init_table
if (info)
show_info_header();
if (info || dump_table) {
//init before adjustment to get the default values
adjerr = ryzenadj_refresh_table();
if (adjerr != ADJ_OK) {
char *er = ryzenadj_error_str(adjerr);
printf("Unable to init power metric table, this does not affect adjustments because it is only needed for monitoring.\n");
printf("%s\n",er);
free(er);
}
}
//adjust all the arguments sent to RyzenAdj.exe
setOpt(ADJ_OPT_STAPM_LIMIT, stapm_limit, &adjerr);
setOpt(ADJ_OPT_FAST_LIMIT, fast_limit, &adjerr);
setOpt(ADJ_OPT_SLOW_LIMIT, slow_limit, &adjerr);
setOpt(ADJ_OPT_SLOW_TIME, slow_time, &adjerr);
setOpt(ADJ_OPT_STAPM_TIME, stapm_time, &adjerr);
setOpt(ADJ_OPT_TCTL_TEMP, tctl_temp, &adjerr);
setOpt(ADJ_OPT_VRM_CURRENT, vrm_current, &adjerr);
setOpt(ADJ_OPT_VRMSOC_CURRENT, vrmsoc_current, &adjerr);
setOpt(ADJ_OPT_VRMGFX_CURRENT, vrmgfx_current, &adjerr);
setOpt(ADJ_OPT_VRMCVIP_CURRENT, vrmcvip_current, &adjerr);
setOpt(ADJ_OPT_VRMMAX_CURRENT, vrmmax_current, &adjerr);
setOpt(ADJ_OPT_VRMSOCMAX_CURRENT, vrmsocmax_current, &adjerr);
setOpt(ADJ_OPT_VRMGFXMAX_CURRENT, vrmgfxmax_current, &adjerr);
setOpt(ADJ_OPT_PSI0_CURRENT, psi0_current, &adjerr);
setOpt(ADJ_OPT_PSI3_CPU_CURRENT, psi3cpu_current, &adjerr);
setOpt(ADJ_OPT_PSI0_SOC_CURRENT, psi0soc_current, &adjerr);
setOpt(ADJ_OPT_PSI3_GFX_CURRENT, psi3gfx_current, &adjerr);
setOpt(ADJ_OPT_MAX_SOCCLK_FREQ, max_socclk_freq, &adjerr);
setOpt(ADJ_OPT_MIN_SOCCLK_FREQ, min_socclk_freq, &adjerr);
setOpt(ADJ_OPT_MAX_FCLK_FREQ, max_fclk_freq, &adjerr);
setOpt(ADJ_OPT_MIN_FCLK_FREQ, min_fclk_freq, &adjerr);
setOpt(ADJ_OPT_MAX_VCN, max_vcn, &adjerr);
setOpt(ADJ_OPT_MIN_VCN, min_vcn, &adjerr);
setOpt(ADJ_OPT_MAX_LCLK, max_lclk, &adjerr);
setOpt(ADJ_OPT_MIN_LCLK, min_lclk, &adjerr);
setOpt(ADJ_OPT_MAX_GFXCLK_FREQ, max_gfxclk_freq, &adjerr);
setOpt(ADJ_OPT_MIN_GFXCLK_FREQ, min_gfxclk_freq, &adjerr);
setOpt(ADJ_OPT_PROCHOT_DEASSERTION_RAMP, prochot_deassertion_ramp, &adjerr);
setOpt(ADJ_OPT_APU_SKIN_TEMP_LIMIT, apu_skin_temp_limit, &adjerr);
setOpt(ADJ_OPT_DGPU_SKIN_TEMP_LIMIT, dgpu_skin_temp_limit, &adjerr);
setOpt(ADJ_OPT_APU_SLOW_LIMIT, apu_slow_limit, &adjerr);
setOpt(ADJ_OPT_SKIN_TEMP_POWER_LIMIT, skin_temp_power_limit, &adjerr);
setOpt(ADJ_OPT_GFX_CLK, gfx_clk, &adjerr);
setOpt(ADJ_OPT_OC_CLK, oc_clk, &adjerr);
setOpt(ADJ_OPT_OC_VOLT, oc_volt, &adjerr);
setOptEnable(ADJ_OPT_CCLK_SETPOINT, power_saving, &adjerr);
setOptEnable(ADJ_OPT_CCLK_BUSY, max_performance, &adjerr);
setOptEnable(ADJ_OPT_ENABLE_OC, enable_oc, &adjerr);
setOptEnable(ADJ_OPT_DISABLE_OC, disable_oc, &adjerr);
setOpt(ADJ_OPT_COALL, coall, &adjerr);
setOpt(ADJ_OPT_COPER, coper, &adjerr);
setOpt(ADJ_OPT_COGFX, cogfx, &adjerr);
if (adjerr == ADJ_OK) { // this is only checking last setopt error
//call show table dump before anybody did call table refresh, because we want to copy the old values first
if (dump_table)
show_table_dump(1);
//show power table after apply settings
if (info)
show_info_table();
}
ryzenadj_cleanup();
return !(adjerr == ADJ_OK); |
@kylon Actually the idea of cleaning up the code and optimizing the library sounds great, but I am categorically against your idea of removing ryzen_access in code. I completely disagree with your opinion that only function calls should remain, although most likely I did not quite understand you. Why do I think so? For example, the use of libryzenadj.dll in apps with several pages, where each page is using Ryzenadj, its easier to use initialization only once, and save this pointer (ryzen_access) and use it across whole app instead of reinitialise it every time after one call. I guess I didn't quite understand your concept. I also have my own fork of Ryzenadj but it aimed only on providing additional information from power table for all supported CPU codenames |
@kylon, thanks, that's awesome! I was trying to implement a string based API but yours stand out. Maybe |
I think current RyzenAdj is indeed not working well in multi instance scenario. IMO we should keep ryzen_access and maintain a reference counter to handle multiple instances and locking. |
@Erruar yea you were supposed to read the code, that was just the hint init is still one time only, just cleaner |
@FlyGoat typed? can you explain? |
You see, for example, I used ryzen_access for checking is Ryzenadj already initialized, if it's not zero, then its already initialized. IDK what about your implementation. But that would be great to see auto initialization. Like you are using get or set command, and in dll code if it wasn't initialized before - its initializing and gave results to main app (results as usual get or set command), if its already initialized - just gave the result of call. I think it will be better realization if you hide ryzen_access from main application. |
Like maybe |
@Erruar thats not needed, i m not sure why you want to check if it is initialized, assuming you dinamically create/destroy pages, init should be in main, so when the app starts auto-init is not good in c library, it must be clear that the lifetime is init-cleanup, if you remove init, it may confuse people or they forget about cleanup @FlyGoat not sure, sounds like duplicating functions, i think the proper way is to return double, if the biggest value you can get from table is double, and let users cast to what they need or just use as is because things will be auto-casted anyway like double->int |
I don't think this is a proper way to do life-cycle management. If, for example, two libraries linked into the same binary are using ryzenadj, then we will run into double free on teardown. I guess we still need some kind of life-cycle management here, ryzen_access object was designed to do it in objective style. We'd better still use object to expose life-cycle to API users, but internally we can just init everything once and use a reference counter to manage objects.
Converting between float and int brings a lot of issues like loosing precision, NaN representation... I think libryzenadj aims to be a slim shim so we shouldn't handle this. |
i think i m not following here, if 2 libs are using ryzenadj, they have their own obj, they will manage it, not the app using the lib, you end up with 2 objs anyway with the exposed access obj and they will free their own, its not double because its not the same memory dont get what the reference counter should do, sounds like a shared_ptr kinda implementation but my point is that access is unique unless i m not understanding what you are saying having to keep access around is a burdern with no clear advantage, for example pci_access (pciutils) makes sense because you need it for various things, but here, access, is just used as parameter for its own calls and i dont think it is good to make smu objs and other things in it public, i mean not directly but in a more structured way, like api to send cmds to smu but not get the smu pointer, if you think it this way, access is no longer valid as a public obj not sure i m explaining this correctly
yes, that was my point, imo ryzenadj should not do this kind of things or have typed functions, it reads the value and thats it, then the user will do what it want with it |
For example, say there are two libraries say it's |
ah yes, right |
Mainly to avoid one instance's failure on life cycle management affect another. This is standard practice for thread-safe libraries. |
I’m excited to share that our project has just hit its 5-year milestone! As part of our ongoing development, I'm planning to release v1.0.0 soon, which will include significant improvements to the codebase. I'd love to get your feedback on the following potential updates:
Please use this issue as a function wish list as well.
Thanks
The text was updated successfully, but these errors were encountered: