#ChronoGrapher - One Unified Scheduler, Unlimited Power
1 messages · Page 6 of 1
finished the dependencies chapter
Are you writing DOC?
?
wdym?
no i don't write API docs
Oh okay
bois
we have to keep moving
we should stop lazing around (even if today i had a tiresome day)
though il be taking a break, a bit of the burnout is catching up
What does the project lacks for the alpha ?
wait hold on imma bunch it up
- Fully-made Proc Macros
- Complete Guidebook + Guidelines (easily ~30-50k words)
- Complete API docs
- All Unit Tests (with every scenario)
- CI/CD pipeline
- The website. (landing page along with some other pages)
- The benchmarks
- A couple of optimizations for the crate to make it run faster
- Fixing the memory leakage bug (though i think we can leave it as is tbh, it won't be that bad since its alpha)
and thats scratching the surface
then there will be:
- Bindings for each programming language (repeat the API & Guidebook docs, benchmarks, optimizations and unit tests steps per binding though not as severe as now)
- Local / Single-node persistence
- TaskHandles
- Abstracting the async runtime
- The Simulacrum
- More optimizations
...etc.
@formal sedge i think you now understand why we need a bit more expertises
than just unit tests and api docs
we really do need em no joke
at best we may even finish September,
1 YEAR
on one project
on the core of all things without an alpha release
I mean
your alpha looks really more as a beta
I know I am too optimistic, but things like Website CI/CD and full test coverage is long
Think that the best thing is to first focus on the core of the core and then integrates things like CI and website when core is in an usable state
Like proc macros is basically the main use of the core API
kinda true
the website though is for marketing
while i could market it as beta
i want to avoid announcing beta as the BSL license would need to promote to MIT
for now il avoid calling it beta and call it alpha
the reason i do wan to keep BSL is its a temporary license, the idea is to discourage competition from just forking it and expanding upon
Fair
what the actual fuck?
im like flasterbanged, what?
im so confused
what does a nutrational company have to do with my product first, second is this advertisment, is this a job like huh?
Why is website has an rust endpoint😂
wouldn't reccomend going in the website
i swear though
their account seems to have been created 4 years ago but recently active in March of 2026
somewhere today they've customized their profile?
@fervent lark i am busy doing assignment and the other work i cant take out time till end of june (its exam) unless its weekend
damn...
this sucks
samy isn't working frequently
and we're like 3 peps
mostly doing the work
ofc
i mean after all you're contributing
not like being payed for it to be more serious
haha i am doing because i want to
no obligation
ye, for that i have some respect
and you're helping for the most part
nahh i am not doing major thing but yes i am doing small small things
i wanna start solving issue
but the problem is my skill are not good enough for now
i finished an intermediate chapter
for schedules
for the guidebook in order to be finished il have to:
- Make the "Introspecting Workfows" chapter series explaining TaskHooks (should be around 5 chapters explaining TaskHooks)
- Make the "ChronoGrapher's Otherside" chapter series explaining the Scheduler side (i'd imagine this will be by far the biggest at a whopping 10 chapters, somewhere there)
- Make a chapter series diving into the Base API (type-level, without the proc-macros that simplify lots of things, should be ~5 chapters)
- Finish the contribution guidelines
- Make a FAQ chapter for common questions
And i think its done
for the entire guidebook series its ~31-35k words in length or about 31-35 chapters
plus the contribution guidelines its 40-50k words
imma pause the guidebook a bit
tbh i really want to do the TaskHandle based approach
i did with the workflow chapters
for the rest like TaskHooks and so on, well not
where can i read ? website ?
ahh i see there is new tasks to workflow
ig it was old well anyway there is dependancy chapter i am looking for
@fervent lark is FrameDependency depends of schedular ?
is scheduler checks is resolve before dispatching a frame?
related to FrameDependency
or else just tell me use of frame dependency trait
is it same as standard dependency in guidebook ?
@fervent lark can i write this
/// A [`FrameDependency`] is base triat for any dependency, it has a single prerequisite
/// that must be satisfied before a task frame is allowed to run.
no the DependencyTaskFrame does that
first typo on trait
second
im not sure about "single prerequisite", keep in mind there are logical dependencies (OR, AND, NOT, XOR)
which im not sure if they count as "single"
man...
i realized
we haven't even touched the main landing page
for ChronoGrapher
man i feel like taking a break
ya you should
happy new month
@jolly frost @placid drum how is progress going?
ngl its quite damming to realize we entered May and the project is still not delivered in its first alpha stage 
il have to geet back to speed things up
no way
-# 60 subscribe special video is coming soon /j
enough though with the jokes
its time for serious effort
🤔 for the TaskHandle i have to think a different approach
back to the drawing board, its just too difficultt o make TaskHandle work with its various lifetime issues and other complexities
though its hared to think a different solution
man this issue is present for months
man i feel empty
like i litterally have no idea on what to do
the problems are complicated af
and im the wost guy to explain these problems to some outsider
@jolly frost you good man?
one idea i do have is moving docs, rn doctests don't work
since they use the crate chronographer and well we are in chronographer_base
wait
it can be fully avoided
i just added chronographer as a dev dependency
im slowly fixing the doctests
il have to also rework on them since im not really sure if they are strict enough
so far i got the errors down to 3 failures (2 of which come from the cron macro not being materialized)
i am looking reworking tests such as this:
/// \`\`\`
/// use std::num::NonZeroU32;
/// use std::time::Duration;
/// use chronographer::task::TaskFrameBuilder;
/// # use chronographer::task::{TaskFrame, TaskFrameContext, FallbackTaskFrame, TimeoutTaskFrame, RetriableTaskFrame};
/// # use chronographer::errors::TimeoutTaskFrameError;
/// # use async_trait::async_trait;
/// # use std::any::{Any, TypeId};
/// # struct MyTaskFrame;
/// #
/// # impl TaskFrame for MyTaskFrame {
/// # type Error = String;
/// # type Args = ();
/// #
/// # async fn execute(&self, _ctx: &TaskFrameContext, _args: &Self::Args) -> Result<(), Self::Error> {
/// # Ok(())
/// # }
/// # }
///
/// # struct BackupFrame;
/// #
/// # impl TaskFrame for BackupFrame {
/// # type Error = String;
/// # type Args = TimeoutTaskFrameError<String>;
/// #
/// # async fn execute(&self, _ctx: &TaskFrameContext, _args: &Self::Args) -> Result<(), Self::Error> {
/// # Ok(())
/// # }
/// # }
/// // `MyTaskFrame` and `BackupFrame` are two impls that implement `TaskFrame`.
///
/// const DELAY_PER_RETRY: Duration = Duration::from_secs(1);
/// # type WorkflowType = FallbackTaskFrame<TimeoutTaskFrame<RetriableTaskFrame<MyTaskFrame>>, BackupFrame>;
/// # type WorkflowPermut1 = FallbackTaskFrame<RetriableTaskFrame<TimeoutTaskFrame<MyTaskFrame>>, BackupFrame>;
///
/// let composed = TaskFrameBuilder::new(MyTaskFrame)
/// .with_retry(NonZeroU32::new(3).unwrap(), DELAY_PER_RETRY) // Failure? Retry 3 times with 1s delay
/// .with_timeout(Duration::from_secs(30)) // Exceeded 30 seconds? terminate and error out with timeout
/// .with_fallback(BackupFrame) // Received a timeout or another error? Run "BackupFrame"
/// .build();
///
/// # assert_eq!(composed.type_id(), TypeId::of::<WorkflowType>());
/// # assert_ne!(composed.type_id(), TypeId::of::<WorkflowPermut1>(), "Unexpected matching workflow types");
/// \`\`\`
bc rn its cumbersome, i mean its shows general use case
but no strict checking
actually wait
il just simplify
I am rethinking to also rework the dependency system
more specifically avoid the use of ArcAtomicBool> everywhere external to the users
for the dependency system, the more i think about it, its useless for it to be a trait
kind of
but its faster to use an enum instead
without sacraficing extensibility
let simple_dependency1 = Dependency::all(&[my_dependency1, my_dependency2, my_dependency3]);
let simple_dependency2 = Dependency::any(&[my_dependency1, my_dependency2, my_dependency3]);
let simple_dependency3 = Dependency::not(&my_dependency1);
let complex_dependency = Dependency::builder()
.task_runs(5) // Resolve when Task runs for 5 times
.task_successes(3) // Resolve when Task runs successfully for 3 times
.task_failures(2) // Resolve when Task runs successfully for 2 times
.consective_task_successes(3) // Resolve when Task runs successfully for 3 times CONSECTIVELY
.consective_task_failures(2) // Resolve when Task runs successfully for 2 times CONSECTIVELY
.timeout(Duration::from_secs(3)) // Resolve after a certain time passes
.dependency(simple_dependency1) // Custom dependencies
.all(); // All sub-dependencies must resolve, there is "any" as well for either one and "not" (requires ONE dependency)
complex_dependency.disable(); // Disables the entire dependency
complex_dependency.enable(); // Enables the entire dependency
complex_dependency.reset(); // Resets the state of the dependency (if it supports it)
honestly idk, there might be a better idea to present these dependencies
while retaining expression
actually operator overloading of BitAnd, BitOr and Not will help
the interface is tbh better with operator overloading
There is operator overloading in rust ?
Overloadable operators.
i am rewriting some of the unit testing for the dependency system
due to the recent changes
im kinda bored af to do but eh
whatever
honestly idk but life's kinda hard, for some reason i feel demotivated to continue the progress i was going with on the project
my contribution rate is in an all time low
honestly i feel like its best to stick to the vision of delivering a basic alpha version, limit the scope drastically
we need see completion to feel the dopamine hit to continue
honestly i should not care for the project personally for like 2 weeks
imma take a break, distracting myself with some smaller project for the dopamine hit then move back
though one thing im kinda wondering is what to do 🤔
i want something more "immediately rewarding", something UI based like an application just have no idea what to do
I am planning to make my own excaclidraw
rn excaclidraw is not too flexible, though its simpler and faster to use over a vector-based editor such as affinity for these diagrams
the opposite holds true for vector-based editors as well
lol
i have conflicting thoughts
i think its time to take care of the website
my conflicting thoughts are to distract myself as a break with some other smaller side project, and then work on ChronoGrapher vs the dreadful delay of ChronoGrapher's first release
its gonna be a huge pain in the ass (and it already is and was) 
though i don't have a clear idea of to present stuff
i kinda have the style just right, though i wanna make it more technological
i like napi.rs style
ya its good
tbh im not that good at stylying it
neither i
lol
so are you working on alpha or excaclidraw ?
ChronoGrapher
i kinda changed my idea and i do want to finish this project's core real soon
Yup
I am reading new blogs and lot of stuff
okie dokie
tbh you will do the API docs like you always did
no i am tired of that
oh
hmmm
you could try to tackle the guidebook docs
how about that?
tbh i really dont understand whole system
eh, understandable but you learn progressively
you don't need to understand the whole thing
you just learn stuff when you need to do something that requires this knowledge
ya ig you right about this
whats task ?
you will have to read the Guidebook Guidelines
to understand how to manage the system
plus you have to know TaskHooks in great detail
after that you will work on the Introspecting Workflows chapter series
feel free to look on previous chapters for the structure and way of writing
okay i will understand and ask you about it
okie dokie
at least learning english properly.
ye
the next chapter should include the following:
- How to create your own
TaskHookEvents - How to emit your own events in places
- What is the
Hook-To-Hook Communicationpattern
and i think it should be it
the chapter title should be something like Custom Events & Communication
hmm got it.
also also
i reccomend taking a stroll on the previous chapters, like read them and critique them
yes need to understand strucutre and overall way of writing style.
ye, also don't critisize them in a shallow manner
explain what you liked versus hated
in detail
point out mistakes... etc.
ya sure
ya i'll finish things in my hand and start working on it.
ok
god its just dreadful to look the calendar, realize its May 16 and the project's core is like 60% finished with the other 40% being a lot of stuff to do (docs, unit tests, CI/CD pipeline... etc.)
why 1st example not working from installation and theory ??
chronographer = { git = "https://github.com/GitBrincie212/ChronoGrapher" }
its macros and stuff
thats why
which aren't implemented rn
like ye there is the every! macro, but no like attribute macros which it does use
need to understand what is pending and completed
i am talking about the macros part need to understand which are completed and which are pending
all of what you see in the guidebook currently is incomplete
you will need to rely on the base API
for instance
oh i see now implementing macros would be fun.
i know if it was easy you have already finished it.
ye
tbh you could try
i'd reccomend start with the "basic" macros
cron! for instance
then work your way up, so you can understand the model better
okay but before i'll finish current chapter i am writing.
btw, this is what the example using macros:
use chronographer::prelude::*;
use thiserror::Error;
// Define the errors our application can throw (for now empty)
#[derive(Error, Debug)]
pub enum MyErrors {}
// Define our singleton Task to run
#[task(schedule = interval(2s))]
async fn MyTask(ctx: &TaskFrameContext) -> Result<(), MyErrors> {
println!("Hello ChronoGrapher!");
Ok(())
}
#[chronographer::main]
async fn main(scheduler: DefaultScheduler<MyErrors>) {
let task_inst = MyTask::instance(); // Tasks defined like above are singleton
let _ = scheduler.schedule(task_inst).await;
}
This translates to base API:
use chronographer::prelude::*;
use thiserror::Error;
pub struct MyTaskFrame;
impl TaskFrame for MyTaskFrame {
type Args = ();
type Error = MyErrors;
async fn execute(&self, ctx: &TaskFrameContext, &args: Self::Args) -> Result<(), MyErrors> {
println!("Hello ChronoGrapher!");
Ok(())
}
}
// Define the errors our application can throw (for now empty)
#[derive(Error, Debug)]
pub enum MyErrors {}
#[tokio::main]
async fn main() {
let scheduler = LiveScheduler::<DefaultSchedulerConfig>::default();
let task = Task::new(
MyTaskFrame,
TaskScheduleInterval::from_secs(2)
);
let _ = scheduler.schedule(task);
scheduler.start().await;
loop {} // It will have "CTRL + C" handling, not just a loop. This is for simplicity
}
you can see why macros are more appealing than writing in raw Base API
also @placid drum what kind of chapter are you writing exactly?
ya macro should be there
the continuation of the TaskHooks? Or something else?
hook to hook comunication
ah ok
also how many words is it?
like an estimate
its highly reccomended to stay in the bound of 1k words each chapter, since its a nice middleground between long enough to cover the details but short enough for the user to sit down, read it fully and feel productive
unless you got a good reason (rare) to do so, such as the topic itself cannot be seperated to smaller chapters, or leave out advanced concepts out of the window for future chapters to cover... etc.
yup i will try that, also read guide book guide lines.
okie dokie
@fervent lark i was reading the prv chapter's to understand how everything works, found out that -
The second phase or known as Registration Phase involves (do you think its correct statement because i dont think so for me ig it should be - its second way rather than phase )
because it dosn't continue with first phase
there is no first phase my bad
there is?
where ?
There are 2 phases to use the TaskHooks, the former is the Implementation / Definition Phase while the latter is Registration Phase, in the first,
gotch
but isnt this is same thing in done different way ?
no?
when you make a TaskHook, its just a container, a definition
it has its data
it has its events to listen to
f* i got it now
but where would it act upon? The answer is on nothing
so you need a Task to mount it / attach it onto
thats why the registration phase is needed, so it knows where it is attached to
phase one create just definition, and then in next phase we attach to task ?
yup
you can attach to multiple Tasks, not just one
more specifically, not only you attach it to a Task, you have to attach it to a specific event
for example
if you attach it on the event OnTaskStart, well it would trigger the code for when said Task starts
OnTaskEnd for when a Task ends, and so on for other events
cool
you don't have to attach the same instance to multiple events, but at least you have to attach it to one
ya understand now
its basically the event listening / observer pattern, just more typed
as opposed to using strings everywhere
one of the problems with this system btw is you have to register manually to every event. Suppose an example like this
impl<E: TaskHookEvent> TaskHook<E> for MyTaskHook {...}
and yes you can use generics to generalize side-efffects to a category of events
this code no matter which event it is attached to, will run the same code (plus it has no idea which event it is)
if you need to narrow, well:
impl<E: TaskLifecycleEvents> TaskHook<E> for MyTaskHook {...}
this runs the same code for OnTaskStart and OnTaskEnd
these are called TaskHook Event Groups (THEGs for short)
no kidding its really cool and complex does people really use this kinda things ?
it ain't really complex on its own
on the surface, TaskHooks are basic
no about the task hook but overall
these are just patterns
i am not able to think where can be use this kinda complex workflow
its about workflow orchestration
il hit you up with a counter argument
say you want to add logging right? Every distributed service, hell even a basic server needs logging for monitoring
ya please give example
yep
how do you execute your own code / side-effects when some Task runs at the start, or finishes?
you need to well, execute your code to log stuff
thats one of the uses for TaskHooks
and generally everything that requires listening to events happening in a system, and reacting is bound by this example
i know the importance of taskhook
another use i can think off the top of my head is suppose a special kind of Task
right now our system treats a Task as a static struct, you have only the TaskFrame, the TaskSchedule and the storage of TaskHooks
but i am talking about the where in industries, workflow orchestration is used like, they only thing i used is cron
its for more complex stuff
cron doesn't scale once your app is discord's level of complexity
or similar complexity
you add distributed systems right? Well, cron doesn't support distributed systems so you need to work around that somehow
then you will need observability, so you'd have to integrate with your library of choice (Prometheus, Grafaana, Datadog... etc.), well there is more work to getting this right
and before you know it, you have made a duct-tape messy workflow orchestrator
really, users like you with basic needs won't ever certainly touch an orchestrator
only once you introduce enough complexity, then an orchestrator becomes viable / appealing
like have you ever used http://temporal.io for basic stuff?
the answer is hell no
its too complex for what you need, but once you scale out, then you will start using temporal more
no
never get chance to work on this scale
ChronoGrapher takes it a step further, instead of having to switch entirely your stack and rewriting countless of duct tape code (not to mention the entire time wasted writing it in the first place)
what if you already start at a small scale
ChronoGrapher is meant to transition smoothly between various scales from smallest to highest, temporal remains a fixed toolkit, ChronoGrapher adapts
thats the philosophy
i hope you get what i mean
damn need to learn more
ngl i didn't knew these stuff either, its only when i learnt that its used, that i figured to start the project
title: Custom Events & Communication
description: Learn how to define your own TaskHookEvents, emit them anywhere in your code and use Hook-To-Hook Communication
In the previous chapter we learn about the two phases of TaskHooks Implementation and Registration where we
saw how to carry state inside a hook, use singletons to share that state across events and Tasks, and learned the
Stateful Container pattern for hooks that hold data without listening to any event.
In this chapter we going to define our own TaskHookEvent, emit them. also we are going to understand how Hook-To-Hook Communication
works.
Defining A Custom TaskHookEvent
To define our custom TaskHookEvent we have to satisfies the TaskHookEvent trait. the trait has associated type Payload,
which carries the data that travel alongside the event when it is emitted:
<RenderProgrammingLanguageBased target_name={"Rust"}>
use chronographer::prelude::TaskHookEvent;
#[derive(Default)]
pub struct OnRowsFetched;
impl TaskHookEvent for OnRowsFetched {
type Payload<'a> = usize where Self: 'a;
}
</RenderProgrammingLanguageBased>
Now let's understand what we wrote. OnRowsFetched is a marker struct, it carries no data itself. The payload, usize, is the
number of rows returned in batch and travels with the event when it is emitted. we will have () valid payload type when event carries no
data at all.
how's this so far
In the previous chapter we learn about the two phases of TaskHooks Implementation and Registration where we
saw how to carry state inside a hook, use singletons to share that state across events and Tasks, and learned the
Stateful Container pattern for hooks that hold data without listening to any event.
I don't think you needed this long ass description to explain what we've done on the previous chapter
i reccomend shrinking it, you can omit some information such as the Stateful Container Pattern
okay
we going -> we are going
also we are going to understand how Hook-To-Hook Communication works. feels abrupt / awkward, try to chain it in one sentence, not to mention also should have been Also but still
satisfies -> satisfy
i am noticing you are too used to API docs
this thing is more narrative than technical manual fashion like API docs
you are focusing on syntax
hmm need to change the approach
To define our custom TaskHookEvent we have to satisfies the TaskHookEvent trait. the trait has associated type Payload,
which carries the data that travel alongside the event when it is emitted:
This also sounds like it has no energy, not to say you have to make it childish
but it feels like you are bored to present the topic
damn bored crazy 💀
ye srry if it sounded offensive
ik you ain't bored
but im viewing it in the eyes of the reader
ya i am good with critics
forces me to push forward
english doesn't come naturally from me its my 3rd language so you know kinda its difficult to explain things, well anyway.
oh damn
3rd
kinda impressive
im rewriting your writing as well as your example
to showcase you what i would do to boost this
ya this would help understand way of writing thank you.
I'd rewrite it in this kind of fashion:
title: Custom Events & Communication
description: Learn how to define and emit your own TaskHookEvents
In the previous chapter we learnt about the two phases of TaskHooks, the Implementation Phase and Registration Phase, we saw how to subscribe to events, sharing TaskHook instances between events and Tasks and even adding data for our TaskHook to hold.
In this chapter we will focus on the event part of TaskHooks, how to create your very own events, how to emit them in your own places and understand Hook-To-Hook communication.
Defining A Custom TaskHookEvent
Defining a custom event for our TaskHooks to listen to is as simple as:
<RenderProgrammingLanguageBased target_name={"Rust"}>
rust title="src/events.rs" lineNumbers
use chronographer::prelude::TaskHookEvent;
#[derive(Default)]
pub struct MyTaskHookEvent;
impl TaskHookEvent for MyTaskHookEvent {
type Payload<'a> = () where Self: 'a;
}
In our example above, we have defined MyTaskHookEvent as a TaskHookEvent, by creating a typical struct and implementing the TaskHookEvent trait. This trait requires the Default trait as such, we derived it.
In our previous chapter you may have noticed a payload parameter for our TaskHooks. This payload parameter is the same as the associated type Payload<'a>, in our case we defined it to () which means it has no data to carry.
</RenderProgrammingLanguageBased>
Payloads are used to hand out extra information onto the TaskHook about the event, they are a transport layer between the cause of the event and the listeners (the TaskHooks). This data can be anything from a simple number to an array of strings to a handler... etc.
don't blindly copy paste this, do examine why i wrote it in this way
also:
In our example above, we have defined MyTaskHookEvent as a TaskHookEvent, by creating a typical struct and implementing the TaskHookEvent trait. This trait requires the Default trait as such, we derived it.
In our previous chapter you may have noticed a payload parameter for our TaskHooks. This payload parameter is the same as the associated type Payload<'a>, in our case we defined it to () which means it has no data to carry.
This is on purpose inside the RenderProgrammingLanguageBased, as it discusses rust specifics not found elsewhere in other programming languages, as such it deserves to be inside there
try to be general about features, don't go to syntax detail and if you do, surround it with a RenderProgrammingLanguageBased. While Rust is the only language for now, the idea is to prevent users from Python having to conceptualize / understand any Rust details
the guidebook adapts per programming language
also keep things incremental
increase complexity slowly, you may notice the Payload wasn't really explained and given an example for an integer value, i would do it later once the user has been accustomed to what the payload is
Yes need to compare with mine understand what should be ideal way and mistakes I did
honestly, this is why i told you to examine the previous chapters
also i would shorten the description
i edited the message with this in mind
Ya did that I was trying to run and understand
also not in terms of the reader, more so open your point of view as a writer
like analyze the structure, presentation... etc.
np
i will be free around in 1hr then start working on it, i try to raise pr.
okie dokie
@fervent lark we can do this right ?
#[async_trait]
impl TaskHook<OnRowsFetched> for FetchMetricsHook {
async fn on_event(&self, \_ctx: &TaskHookContext, payload: &<OnRowsFetched as TaskHookEvent>::Payload<'*>) {
let total = self.total_rows.fetch_add(*payload, Ordering::Relaxed) + *payload;
println!("Batch: {} rows | Running total: {total}", payload);
}
}
yup
also
OnRowsFetched isn't a good example of an event
thats like database stuff, they aren't really known for events and stuff
try to write the examples in a way which users would encounter in real code, not too realistic with its complexity but not too hypothetical / academic
also \_ctx: &TaskHookContext?
like just write _ctx: &TaskHookContext
okay
@fervent lark created pr
ok
check please give me feedback on this thanks.
ye im checking it
first of
. and saw how to carry and share state across events and Tasks.
like use a comma
again you seem to focus on syntax
nvm
my bad
i tried to sift my focus lot of time what should i say but i tried my best
well i didnt use ai well i use for example change
semi-good you didn't use AI, would reccomend using AI though as like a "polish" tool, the idea is to iteratively ask it gaps to identify then it gives you those gaps, which you filter these out depending on if they are bullshit or genuine feedback, then you fix them on your own and rence and repeat
but you NEVER blindly follow the AI
The `ctx` argument provided to every task is a `TaskFrameContext also this is filler
the user knows about the context object, no need to re-explain it
okay so we are skiping things happen in prv chapter ?
you can just mention, "TaskFrameContext contains various methods for us to use, but in our case we will be using emit"
ye
no need to constantly re-explain
unless its summary or anything like that, but in this case its not
make sense i was under the impresion that we have to just ignore the basic stuff
also the example you provided while semi-good
with the notifications
i suggest we can take it a step further
rn its not working, why don't you just include the method with some very basic code
it shouldn't be something too complex, just showcase the payload is changing
also i reccomend a shorter gap, not 10 entire seconds, make it like 2
since the user will most likely run this to understand, and feedback is important
Now we -> Now that we
but we still lack the main component -> ..., we still lack the main component
okay
which is listeners -> which are listener (more specifically our TaskHooks)
code. we need -> code. We need
if there is no listeners ? -> if there are no listeners?
avoid so as a filler, sounds too casual
just follow it immediately ChronoGrapher handles this without any overhead by simply doing no-op.
again you re-state TaskHooks which was covered by the previous chapter
i wouldn't reintroduce to the user how to write TaskHooks, just write the TaskHook and appending code and saying before all that "For our case we will attach one listner (TaskHook) to monitor our notification task"
okay
for
#[hooks({ OnNotificationsSent: NOTIFICATION_METRICS })]
You can avoid the LazyLock and simply use scoped singletons
discussed in the previous chapter
we have successfully implemented receiver on our custom event -> we have successfully implemented a receiver for our custom event
i am also noticing it sounds static-y, no smooth transitions
keep in mind this isn't API docs where you're free to even talk in a "robotic / lifeless" perspective (again, don't mean to offend there)
you have to tie the ideas in a cohesive narrative
its like writing a novel / a story. You don't say
Jack ran our machine and saw the gears turning, now jack moved to heating the engine
You say:
Jack ran our machine and saw the gears turning, jack then proceeded to heat the engine
mostly talking about the Hook-To-Hook communication pattern. This also saves you the explaining you would have to do (though doesn't seem like it). For example you could narrate it like so:
As shown in the terminal's outputs, we have successfully implemented receiver on our custom event. Now that we have discussed about custom event creation and how to emit them, a key question arises:
> "In which places we can emit our custom events for our receivers to listen?"
The answer is as long as you have access (even indirect through the context) to the Task, you can emit an event. This is where <Highlight color={"info"}>Hook-To-Hook Communication</Highlight> comes in picture.
In essence, its nothing more than emitting events in our TaskHooks, then other TaskHooks can listen to these events (and even emit their own events) which allows for a ripple effect to occur between TaskHooks.
This pattern is useful when TaskHooks aren't familar with the presence of other TaskHooks but may want to communicate with the unknown number of other TaskHooks by broadcasting information in a ping-pong fashion.
you can clearly identify there is cohesion, it ties the whole picture for the reader
the example ngl is a really good one
it shows one potential application of how TaskHooks would use, even if the code is "hypothetical"
for the last terminal output, add more sauce
elabrorate more
don't have 2 continiuous codeblocks (rarely you ever need to do that), break it up with paragraphs explaining the code
for bottom stuff, add a line via ---, then summarize the entire chapter and tease the user for the next chapter. Enrich the TL;DR
@placid drum i presume this feedback is extremely detailed, right?
yes i improve and create pr
@placid drum you improved the PR?
i've noticed some other stuff
such as indentation
#[async_trait]
impl TaskHook<OnHighVolumeBurst> for AlertHook {
async fn on_event(&self, _ctx: &TaskHookContext, payload: &<OnHighVolumeBurst as TaskHookEvent>::Payload<'*>) {
println!("ALERT: high-volume burst of {payload} notifications detected.");
}
}
impossible syntax '* -> '_
and quadruple backticks
Sorry I am sleeping right now
no worries
just did pr @fervent lark
also can you add what will be the next chapter about ?
we have a big problem with contribution rn
its not just slow
samy is leaving
honestly i can do so much, im also in the burnout phase
@placid drum do wish for the time being, for you to be consistent (not nescarilly perform the best but finish stuff, its better than sudden bursts) as these are kind of had times for the project
i will try my best
tbh il be busy, so i can't do much personally
critiques are something i can handle ofc, but when it comes to implementing stuff, thinking about the problems rn my time isn't particularily free
i will give as much as time possible, but i cant commit to it 100% i have other things which need lot of attention for now.
ye sure
same here
I do notice in your PR a lot -
prefer dots and commas, seems chatgpt-ish styled
ya did use.
also As see output in terminal you may have notice few let me that to you, ????
it kinda doesn't make sense
though these stuff i presume i can polish them myself as well
again, the TL;DR is focusing on syntax
> **TL;DR.** Custom events are plain structs implementing `TaskHookEvent`, with an associated `Payload<'a>` for whatever data the event carries. Any Task can `emit` them, and any TaskHook can listen. Because hooks themselves can emit, you get Hook-To-Hook Communication which is truly powerfull.
avoid syntax mentions on generic paragraphs, use them only via RenderProgrammingLanguageBased block
okay i will do that.
also told you about the teasers for the next chapter
-# though i haven't established what the next chapter would be, i guess its a good time to think about it
btw how is the content of the guidebook plus the guidelines out of 10 (in depth) and why?
also i do start to realise setting a higher bar in terms of strictness would also be needed in the guidebook guidelines, its harder than API docs as its more narrative focused and varries on the topic
Well it's pretty good I would argue it's best but I will give 9/10 because let's say user want to learn only hook to hook communication their might be things happen in prv chapter. He needs to skim through every chapter.
I would recommend to polish chapter for me. It will become endless cycle of doing things.
I would try to write cron macro. Just tell does it come in core or in different place?
like wdym?
tbh there is a TL;DR and summary so this help to somewhat midigate, for the previous chapters its by design since it targets new users as well
and yes il polish it up
merged
i am thinking of completing the basic functionality we describing in guidebook using macros
so i am thinking starting with macro
the cron! macro's functionality will be used in:
#[task(schedule = cron(...))]
async fn MyTask(...) -> Result<...> {...}
its one to one
as such make the cron macro adaptable as well with minimal changes, so i can directly embed its functionality there.
Or actually the more i think about it, what if we play it more clever
what if its just directly copy pastes whatever schedule contains, this way we both support external schedule types
BUT
for cron, intervals... etc:
#[task(schedule = cron!(...))]
async fn MyTask(...) -> Result<...> {...}
use the function-like macro version
since it copy pastes it, no extra functonality will need to be implemented
okay, i'll look into it.
@placid drum
i published the polishing
actually wait
for some reason RustRover doesn't want to push the changes and is analysing the code
for an SVG
nvm
ok done, take a look on how it is
okay i'll check that out
For the task macro, im not sure if it would be useful but what if you could group methods?
like
pub struct MyTaskGroup {...}
impl MyTaskGroup {
#[task(schedule = ...)]
pub async fn MyTaskA(...) -> Result<...> {...}
#[task(schedule = ...)]
pub async fn MyTaskB(...) -> Result<...> {...}
#[task(schedule = ...)]
pub async fn MyTaskC(...) -> Result<...> {...}
}
imagine sort of "scoped globals"
its will give access to MyTaskGroup self so it will have its access.
ye
it could even inject it into the arguments automatically
ya make sense
im gonna definitely have an issue with schedule
why we dont have 50 hrs a day why??
i also wish
or no need of sleep.

I finished the task macro
#[task(schedule = every!(1h))]
pub async fn MyAbcTask(ctx: &TaskFrameContext) -> Result<(), String> {
todo!()
}
beautiful isn't it?
its also clever in that if it detects you have a task appended at the end of any capitalization and replaces it with Task
you can also configure it to be a singleton or multi-instance based
as well as inject more arguments. Though for Tasks like these, it errors out
you'd need a workfow to do that
also subsequently, the taskframe macro is done
that was quite quick ngl
it took me one afternoon to implement 2 proc-macros
#[taskframe]
pub async fn MyAbc2TaskFrame(_ctx: &TaskFrameContext) -> Result<(), String> {
todo!()
}
i haven't added unit tests for rapid development reasons
also @formal sedge if you're thinking about unit tests, its gonna be a whole lot easier to test both at once over 2 seperate implementations
Oh
for taskframe do it typically, for task however just check the attribute params if they work, the return type when fetching or creating a Task instance and ig the frame type as well, and you're done
I totally forgot about the project😅
lol
How it’s going?
eh not so great
Lack of contributions ig?
ye
samy is taking a backseat due to heavy work he has
so im left with blackpearl as the only contributor
macro's are beauty
didnt get to checkout hopefully i today get enough time to checkout
"hopefully"
let me guess u didn't
i am in meet right now also
i never get offended
someone said to me this - if someone specially dosen't says to you, dont assume it because we only think from our perspective
i am becoming philosopher ah nooo
kinda deep
changed the 2 proc-macros
#[taskframe(name_override = MyCoolThing)]
pub async fn MyAbcTask(_ctx: &TaskFrameContext) -> Result<(), String> {
todo!()
}
#[task(
schedule = every!(1h),
task_override = HelloWorld,
taskframe_override = HelloWorld2,
)]
pub async fn MyAbcTask(_ctx: &TaskFrameContext) -> Result<(), String> {
todo!()
}
Now you can do these stuff
Im also planning to support generics / lifetimes / const generics (with of course the where clauses)
ngl oh my
its so ergonomic
lifetimes sadly aren't really supported
due to 'static lifetime limitations
so only const generics and type generics
like writing this:
this thing:
#[taskframe(name_override = "MyCoolThing")]
pub async fn MyAbcTask<T: Send + Sync + 'static>(_ctx: &TaskFrameContext) -> Result<(), String> {
todo!()
}
Litterally translates to:
pub struct MyCoolThing<T: Send + Sync + 'static> (PhantomData<( T )>);
impl<T: Send + Sync + 'static> Default for MyCoolThing<T> {
fn default() -> Self { Self(PhantomData) }
}
impl<T: Send + Sync + 'static> Clone for MyCoolThing<T> {
fn clone(&self) -> Self { Self(PhantomData) }
}
impl<T: Send + Sync + 'static> Copy for MyCoolThing<T> {}
impl<T: Send + Sync + 'static> TaskFrame for MyCoolThing<T> {
type Args = ();
type Error = String;
async fn execute(&self, _ctx: &TaskFrameContext, args: &Self::Args) -> Result<(), Self::Error> {
todo!()
}
}
generics are tricky to get them to work with the task proc-macro
specifically singleton tasks
ngl it was suprisingly easier than i thought
it only took 3 days (and yesterday wasn't a productive day since i had irl stuff for most of the day)
the code's quality is questionable at best, due to rapid prototyping, i could try to clean it but im lazy rn
i've made more issues for the macros https://github.com/GitBrincie212/ChronoGrapher/issues/179 and https://github.com/GitBrincie212/ChronoGrapher/issues/180
High-Level Overview #[task(...)] Allows users to write their Tasks as an async function and have it translated directly to structs and trait implementations with support for unsafe, generics... etc...
@placid drum i pesume you will be working on the cron! macro right?
I'm asking since i am planning to touch upon it and finish it, if you do however work on it then il try something else
yup
has to do with these two issues
mostly cleaning up the code, adding unit tests and API docs
why though?
to understand what you do
ah
man its kinda crazy how this project has 1475 commits
a lot of work (though most like 40% are smaller changes but still)
so i will start to work on cron
okie dokie
we will do fast enough
try to rapidly prototype it even with bad code then fix it
its gonna save you headaches of "How do i make this prettier" when you implement the features
Make it right, fix the bloat and smells later
okay
@fervent lark sorry for asking agian i have to get requirement clear about the cron
you can refer me to the preve convo also
.
this is correct right ?
hm?
ye
basically
the cron macro hosts, well the cron syntax
cron!(* * * * * *)
like so
actually wait
ignore this
@placid drum my bad ignoe the above
you'll just have to make the cron!
thats all
utilizing the same stuff as the TaskScheduleCron without copy paste
for #[task(...)] macro, well it can be used directly so no need to care for it at all
so i have to just improt the TaskScheduleCron
use proc_macro::TokenStream;
use quote::quote;
use syn::{LitStr, parse_macro_input};
pub fn cron(input: TokenStream) -> TokenStream {
let parsed = parse_macro_input!(input as LitStr);
TokenStream::from(quote! {
chronographer::task::schedule::TaskScheduleCron::from_str(#parsed).unwrap()
})
}
i saw the impl in every so ya
hell no
okay
given a cron expression (NOT a string) like the above
you are supposed to convert it to the CronFields
then pass them to the TaskScheduleCron
oh now i under stand what you
while also erroring at compile-time whenever or not the cron syntax is valid
rn this doesn't do it
it just copy pastes it blindly
resulting in runtime errors which is something to avoid especially in the macro at all costs
if i wrote
cron!(Here is something that breaks it)
Then it would blindly paste it, not erroring me at compile-time
can i just use cron parser from the base by improting ?
you could
though not sure
i think i said something about the cron stuff
i don't want to expose the cron interpertation to the user
so you might need to package it as a seperate crate in the workspace
copy paste it there, include it both on the base API as well as the proc-macro API
and use it from there
one of the problems you will definitely encounter is the fact the lexer / parser is made for strings
and not for tokens, when it comes to errors, you will need to specify where that thing is you want to highlight with the error, with String you lose this information
there will space b.w right just split and use it ig
?
in the cron expression
you mean the cron expression will be split by space?
ya
ye ofc
thats your only splitter
i reccomend looking the every! macro btw
you will notice it highlights errors exactly where they occur
okay!
ya did
okay.
if you plan to re-use the cron interperter (which is reccomended)
I do suggest for you to rework it in such a way
you can adapt it to the macro land
like don't just leave it as is
okay
also about the new crate in workspace are you refering to create the cron and move the cron related stuff there ?
kinda
im referring specifically to move the internals of cron there
the CronParser, CronLexer and the cron translation
so should make change in base to ?
ye, you should include this "hidden" crate
on both it and the macros
okie dokie
for the translation layer of cron, well its embedded deep to TaskScheduleCron
so you will need to make it its own layer
but let TaskScheduleCron and the macro use it with modifications
I think i have an idea
on how you could unify these two in one
go ahead listening.
let the lexer translate the rust expression and strings into an IR (Intermediate Representation)
this IR hosts both span information (if included), and everything common to both strings and macros
then you can work with the IR. When its time to throw errors, throw them with all the information
in the macros, you will extract these and translate it over to a syn::Error compile-time error
in strings you ignore most stuff
i think this should save you more time than if you were to manually remake the cron interperter
plus save me from tech debt
okay sounds like an idea.
also should i proc-macro2 = "1.0.101" in wroksapce and use in /proc and /cron like it needed to space
what?
i am saying is that should i add pro-macro2 in workspace i needed in the new cron crate also need this error
also i've pushed more changes
don't add proc_macro2 in the entire workspace
okay i'll add in cron crate
Rust requires proc_macro to be true
name it chronographer-utils
okay
added API docs for every! function-like macro as well as task and taskframe attribute macros
i kind of strayed away from the API doc guidelines, i see a couple of issues with it, it assumes a particular shape of macros and isn't explicit
i plan to update them to reflect more explicitness and allow for broader macro shapes (while retaining strictness on defining them)
also i do want to make the macros more powerful
one idea for both is the template macro
it modifies the constructor to require arguments up front, though requires non_singleton for the Task
then these constructor parameters can be used inside the function
#[taskframe]
#[template(data1: u8, data2: Vec<u8>, data3: String)]
pub async fn MyTask(_ctx: &TaskFrameContext, arg1: u8, arg2: String) -> Result<(), ...> {
println!("{data1:?}"); // Using constructor parameters
}
other ideas include grouping Tasks with a struct or enum allowing them to share state with each other:
#[derive(TaskGroup)]
struct MyTaskState {
data1: u8,
data2: Vec<u8>,
data3: String
}
impl MyTaskState {
#[task(schedule = every!(1s))]
pub async fn MyFirstTask(&self, _ctx: &TaskFrameContext, ...) -> Result<(), [ERROR]> {
todo!();
}
#[task(schedule = every!(2s))]
pub async fn MySecondTask(&self, _ctx: &TaskFrameContext, ...) -> Result<(), [ERROR]> {
todo!();
}
#[task(schedule = every!(3s))]
pub async fn MyThirdTask(&self, _ctx: &TaskFrameContext, ...) -> Result<(), [ERROR]> {
todo!();
}
}
the entire struct is shared all at once with each Task inside the impl
additionally the macro attaches more methods for working with the entire group at once, such as scheduling all of them simultaneously
but both are just ideas not yet formalized, these will definitely not be included with the first version of ChronoGrapher as they are too complex to make for now
Ladies and gents
#[chronographer::main]
async fn main(scheduler1: DefaultLiveScheduler<Box<dyn TaskError>>) {
todo!()
}
the main macro is finished
you can inject any number of arguments as long as they are schedulers, they will be automatically initialized and started
you can return a Result<T, E> type and use ? in the code just like the main function via thread_count
you can specify the thread count and even the ordering for starting the schedulers (whenever or not to start before or after the user code)
additionally made an issue:
https://github.com/GitBrincie212/ChronoGrapher/issues/181
High-Level Overview #[main(...)] Allows users to write the main function / entry-point of their application, automatically initializing their schedulers, keeping the core loop alive by listening to...
honestly we are getting closer and closer to the first release which is good
good progress i am stuck at the cron level
lol
you got any questions so i can help out?
ngl this summer will require a lot of progress, we've still got outside stuff to handle apart from coding the core library
no question just wtf i am doing and why like
okey so
my plan is to take out cron lexer keep it in utils where i created the
fn tokeniz from token
such as the CI/CD pipeline, the Guidebook Docs, the Website
?
but the problem i am not able to visualize the flow to see oh ya this fn should do this and that is this work
its more like i am hitting the blank wall
did i explian correctly ?
ahh i dont know how to explain better, srry about that.
somethinng like this to have interface we can directly work with the stream
is it correct direction ?
yup
i haven't viewed the entire code
but whatever the case, your job is to convert it to an IR of tokens that being adaptable for both macros and String use case as well
okay
when you get an error in the cron expression, always include additional information like span so the macro can report correctly
wait you finished the entire thing?
nope not entire
ah
wroking on parser
for tests when it comes to the components inside the utils crate
im not sure...
for macros and stuff look in the tests/ folder, there is ui/ and src/macros
okay
added API docs for the main macro
@fervent lark added pr for the cron but didnt change in the base
i though take your approval first then change
ok
i see something i don't really like, you have 2 methods in base for tokenization
i reccomend tokenize_from_tokens should live in proc purely
and the other tokenize_from_str can live there
also you convert to string always
like this is an identifier:
let token_type = match s.to_uppercase().as_str() {
"L" => TokenType::Last,
"W" => TokenType::NearestWeekday,
"SUN" => TokenType::Value(1),
"MON" => TokenType::Value(2),
"TUE" => TokenType::Value(3),
"WED" => TokenType::Value(4),
"THU" => TokenType::Value(5),
"FRI" => TokenType::Value(6),
"SAT" => TokenType::Value(7),
"JAN" => TokenType::Value(1),
"FEB" => TokenType::Value(2),
"MAR" => TokenType::Value(3),
"APR" => TokenType::Value(4),
"MAY" => TokenType::Value(5),
"JUN" => TokenType::Value(6),
"JUL" => TokenType::Value(7),
"AUG" => TokenType::Value(8),
"SEP" => TokenType::Value(9),
"OCT" => TokenType::Value(10),
"NOV" => TokenType::Value(11),
"DEC" => TokenType::Value(12),
_ => return Err((CronLexerError::UnknownCharacter, ident.span())),
};
i also notice you uppercase which you assume the shape is either all lowercase or all uppercase
what prevents me doing? MoN
apart from those innacurarcy, it seems relatively good
while i saw spans in the macro version for errors which is always good, i presume you handle them correctly, not lazily random spans or too general ones?
@placid drum do i merge it or are you planning to modify more stuff?
i will add other stuff like test and make it cleaner.
its for the base
right now we have more code duplicated so yah.
ok
tbh do fix those issues and il merge it
okay
also once you finish the cron stuff
you might want to tackle the unit tests for the rest 3 macros
or smth like that, i think i've already said what you would do next, don't remember
also il not be availaible much as irl stuff kicks in
okay np
i'll check back history.
okie
il try to do smth today for ChronoGrapher
not sure though
no worry
also used darling to make the code more pretty and mantainable
@placid drum if you are wondering what is darling and if you might use it, well, the only time you will need to use it is for attribute macros (maybe derive macros) and specifically ones with attribute parameters. You will probably never need to touch it in your life
okay
ig i would be lot busy today
same for me but for quite the time
the good news is summer is approaching
so once i finish the irl stuff, il have basically unlimited time
the problem will be we will need contribution help
this summer period is quite important for the project, since everyone is free and its Septemember is approaching, we must take full advantage of it
and get the core shipped
we just finish summer
did not got leaves
@placid drum ?
yes
I didn't get what you said
oh so i am saying i dont have leaves so yah.
what are leaves?
am i stupid or smth lol
are you talking about this https://crates.io/crates/darling
crates.io serves as a central registry for sharing crates, which are packages or libraries written in Rust that you can use to enhance your projects
yes
my bad its leave
the workflow macro is gonna be complex 💀
everything i tackled was a toy compared to this beast
But it will make choreographer really good with dx
ye for sure
i would prefer any day writing:
#[task(schedule = every!(2s)]
#[workflow(
timeout(5s)
dependency(MyDependency1 || MyDependency2),
fallback(my_taskframe1, my_taskframe2),
retry(5, 2s)
)]
async fn BackupSystem(ctx: &TaskFrameContext) -> Result<(), String> {
// ...
}
than:
pub struct BackupSystemTask;
impl BackupSystemTask {
pub fn new() -> Task<
TimeoutTaskFrame<
DependencyTaskFrame<
FallbackTaskFrame<
FallbackTaskFrame<
RetriableTaskFrame<BackupSystemTaskFrame>
>,
[my_taskframe2 TYPE]
>,
[my_taskframe2 TYPE]
>
>
> {
Task::new(
TimeoutTaskFrame::new(
DependencyTaskFrame::new(
FallbackTaskFrame::new(
FallbackTaskFrame::new(
RetriableTaskFrame::new(
5, Duration::from_secs(2),
MyTaskFrameTaskFrame::default()
),
my_taskframe1
),
my_taskframe2
),
MyDependency1 | MyDependency2
)
Duration::from_secs(5)
),
every!(2s)
)
}
}
#[derive(Default, Clone, Copy)]
pub struct BackupSystemTaskFrame;
impl TaskFrame for BackupSystemTaskFrame {
type Args = ();
type Error = String;
async fn execute(&self, ctx: &TaskFrameContext, args: &Self::Args) -> Result<(), Self::Error> {
// ...
}
}
this is like 100x more mantainable (the macros stuff)
and this is like "basic" workflows
well not truly simple retries and that, but its typical stuff
like this is kind of expected for something serious
you would need to configure a timeout to prevent tasks from hanging, fallbacks for error handling and retries as a first "weak" layer of defense if its an API request or I/O stuff which can fail but can be resolved by simply retrying
hooks compound this further, if you are more serious about metrics, logging and alerts. You would need to attach various TaskHooks onto the Task
and this is scratching the surface
i haven't included singleton stuff
imagine with generics as well, reordering the workflow, changing the arguments of the backup TaskFrame
you'd have to change a monlith
imagine making the same Task types with a couple of changes over and over again
the workflows retry parsing has been finished
same with delay and timeout
Damn you are fast
timeout and delay are almost identical in parsing
okay but still
i've also finished fallback
the reason i finish so fast is because i make/use "tools" to simplify my life
instead of copy-paste code everywhere and cleaning it afterwards
nice
Its better to have stuff like this
pub struct RetryArguments {
max: syn::Expr,
delay: Option<RetryDelay>,
when: Option<RetryErrorFilter>
}
impl Parse for RetryArguments {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut argument_parser = ArgumentParser::new(input);
let max = argument_parser.parse_required::<syn::Expr>("max")?;
let delay = argument_parser.parse_optional::<RetryDelay>("delay")?;
let when = argument_parser.parse_optional::<RetryErrorFilter>("when")?;
Ok(RetryArguments { max, delay, when })
}
}
an argument parser that does the parsing for me
i just specify the structure to parse as, and it automatically does it for me
handling commas and so on
you should try to adopt this mindset of simplifying as much as possible your life for making features faster
you could finish cron! way quicker if you think about the path to least resistance (without bloat)
i've also made the parsing for retries and so on more strict
it doesn't accept just anything, max is required to be LitInt or either a closure, an ident or macro
finshed also threshold
tbh if we ever want more workflow primitives in the macros for a new TaskFrame, we can simply add a new one
its really simple
sure some definitions repeat, its not 10/10 best but who cares
its more mantainable than other patterns
sometime DRY dosnt work
depends
if it doesn't work, you usually are the problem
the abstraction you set prevents you
more abstraction create more problem which can be solve if its not abstracted (deep enough)
actually not really meant abstraction
was more talking about the approach
you take to solving the problem
also i finished condition. The last stuff are dependency and inject
inject just takes a closure of the TaskFrame and dynamically injects extra TaskFrames to it
but i feel both are fine but depends how often it get change
the dependency workflow primitive is done
everything except inject is done basically
one hard part is gonna be figuring out how I can transform my AST into code
utilizing ChronoGrapher's stuff
i got a Vec of the workflow primitives
wait
i've "sort" of finished the translation of the retry workflow primitive
the only thing left is translating the error filter into match arms
finsihed the retry macro's translations
ngl holy smokes even with just one retry, its ergonomic
it produces this closure
|x: MyTaskFrame| {
RetriableTaskFrame::builder()
.frame(x)
.retries(NonZeroU32::new(1).unwrap())
.when(|err| { !matches!(err , Some (0..=10 | 50 | _)) })
.build()
};
yes ik i have used integers, but thats for testing
all this from:
#[workflow(
retry(1, 2s, when = [0..=10, 50, _])
)]
-# Discord's syntax highlighter acting wonky
Give or take, i had to do some voodo magic to get there
and still have to
i have to parse this thing into my own AST representation, then once done, translate it back to tokens specifically the Base API equivalent
the closure is also done on purpose so its independent from the #[task(...)] or #[taskframe(...)] macro
-# (I do feel as if we are gonna have a problem with singletons in the workflow macro, yuck....)
Its beautiful
|x: MyTaskFrame| {
FallbackTaskFrame::new(
FallbackTaskFrame::new(
FallbackTaskFrame::new(
RetriableTaskFrame::builder()
.frame(DelayTaskFrame::new(
TimeoutTaskFrame::new(
x,
Duration::from_secs_f64(72000f64),
),
Duration::from_secs_f64(43200000f64),
))
.retries(NonZeroU32::new(1).unwrap())
.when(|err| matches!(err , Some (0..=10 | 50 | _)))
.build(),
MyTaskFrame1,
),
MyTaskFrame2,
),
MyTaskFrame3,
)
};
all from this
#[workflow(
timeout(20s),
delay(500ms)
retry(1, 2s, when = [0..=10, 50, _]),
fallback(MyTaskFrame1, MyTaskFrame2, MyTaskFrame3)
)]
i think proc macros really cool ngl (with a slight asterisk on some stuff), i think i found a new addiction lol
dependency is also finished
@fervent lark getting error cron - Unexpected token sequence found everything is correct building correctly but dont know somehow its giving me error.
?
what did you do exactly?
show the full error
its in my pr btw but i am not able to figureout why its giving me this
there is no build failing
also its come from rust analyzer
can't help tbh if i don't have the full thing
it is the error come when i use on
let schedule = cron!(* * * * * *);
on first * Unexpected token sequence found i get this
i'll figureout somthing
i have the suspicion its a .parse() method failing
when you're translating the tokens into your own stuff
okay i'll check that out
got it fixed?
got no time, i will try today
ah
@fervent lark solve the issue but did not replace with base
?
wdym did not replace with base
replace with already exits cron in base ?
its gonna require some funky stuff
like looking specifically for the workflow attribute, then taking it, along with all of its parameters, parsing and translating it from within the macro (task or taskframe) and then modifying
new issue bois https://github.com/GitBrincie212/ChronoGrapher/issues/183
for the workflow macro one of the ideas is to have an internal parameter lets call it __internal_workflow_spec inside the #[taskframe] macro
for the task macro, we would take the everything the workflow has and copy paste it in the parameter no questions asked
then taskframe could parse it on its own
for the taskframe macro though, there are two techniques, either make an internal macro to initialize taskframes or
make the taskframe auto-detect the workflow, extract the tokens and parse it
most likely the second choice is easier
the code is atrocious
name it main
damn i run cargo fmt
in workspace its disaster
@fervent lark
you can merge pr its done from my side
its took while
if it possible you can review it
what?
odd
sure thing
ok seems good enough to merge
one thing @placid drum, you should add API docs as well for the cron! macro
man... This is gonna be pain in the complete ass
the workflow stuff i mean ofc
apparently i've realized there is a "slight" limitation
in the workflow macro
specifically everything that uses a TaskFrame, for example in fallback and condition
there isn't a way for me to distinguish between using the macro to create a TaskFrame or manual implementation
one solution i have is by default all TaskFrames use the default method
however... If you want to use the workflow of another TaskFrame, you can create a TaskFrame instance
i could allow this
or
another way is to use a special symbol to indicate TaskFrames that want to only use their code and not the workflow itself
idrk
Sadly... There is a real limitation
in the workflow macro
fallback(...) cannot inherit workflows from its fallbacks
More specifically:
#[taskframe]
pub async fn MyTaskFrame1(ctx: &TaskFrameContext, error: TimeoutTaskFrameError<String>) -> Result<(), String> {
Ok(())
}
#[taskframe]
#[workflow(
retry(3),
timeout(5s),
fallback(MyTaskFrame1)
)]
pub async fn MyTaskFrame(ctx: &TaskFrameContext) -> Result<(), String> {
todo!()
}
The above works like a charm, but below doesn't:
#[taskframe]
#[workflow(...)]
pub async fn MyTaskFrame1(ctx: &TaskFrameContext, error: TimeoutTaskFrameError<String>) -> Result<(), String> {
Ok(())
}
#[taskframe]
#[workflow(
retry(3),
timeout(5s),
fallback(MyTaskFrame1)
)]
pub async fn MyTaskFrame(ctx: &TaskFrameContext) -> Result<(), String> {
todo!()
}
You only copy raw code from MyTaskFrame1 and not the entire workflow
advanced fallbacks will need to deal with the base API (if each fallback has its own TaskFrames), or when i get to work the inject(...) primitive
i really hate this limitation tbh
i despise it basically
i added an issue just for this in case anyone complains about it
actually its solveable
the only problem btw is now you can't know the concrete type of the workflow
its just impl TaskFrame
but at least workflow inheritance is possible
one of my absolute favourite things is generic workflows
#[taskframe]
#[workflow(
retry(3),
fallback(TF::default())
)]
pub async fn MyTaskFrame<TF>(ctx: &TaskFrameContext) -> Result<(), String>
where
TF: TaskFrame<Args = String, Error = String> + Default
{
todo!()
}
to my brain its really cool to be able to configure the workflow primitives with generics
also one issue i've noticed is when using timeout or anything that can cause an error on its own
well... you'd have to manually type in the return Result<T, E> the TimeoutTaskFrameError<E>
as an error parameter for say a fallback its ok
one idea i have is for the macro to automatically do it for you and map it to the corresponding error
One idea btw il have in the backlog is making TaskFrames such as in the case of fallback allow to append on top an additional workflow
the way i'd do this might involve having a workflow! macro
yes there will be two macros for attribute and function-like
the idea of the function-like version is for users to be able to use it in scenarios where using the attribute version doesn't suffice
also this is kinda annoying:
@placid drum btw
for the next task i don't have anything in mind specifically, except unit tests but you aren't as interested to tackle them ig
I will do API docs for the cron! macro
i was thinking same why not finish cron! api doc
the workflow macro has one WIP area, specifically condition but its kind of a mind fuck for you to solve (same as me)
okay
found an issue with the cron! macro
constants like MON aren't positional
meaning you can apply MON to year and its fine
hm we've got a problem...
the task macro is forced to be in the shape of Task<impl TaskFrame>, everything is basically erased
oh okay