Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add events to Expression 2 #2452

Merged
merged 12 commits into from
Nov 27, 2022
Merged

Add events to Expression 2 #2452

merged 12 commits into from
Nov 27, 2022

Conversation

Vurv78
Copy link
Contributor

@Vurv78 Vurv78 commented Oct 13, 2022

image

This PR adds events to E2. The hope is these will make the current confusing/limited/awful clk system obsolete.

Event List

Pretty small right now, but eventually it should be 1:1 with the current clk setups.

  • tickClk() -> tick()
  • fileClk() -> fileErrored(sn) & fileLoaded(ss)
  • fileListClk() -> ❌ Niche usecase, can be done later if needed. Also want to replace the file functions with lambda ones in the future.
  • inputClk() -> input(s)
  • keyClk() -> keyPressed(esns)
  • chatClk() -> chat(ssn)
  • dsClk() -> ❌ Niche usecase, can be done later if needed.
  • egpQueueClk() -> ❌ Niche usecase, can be done later if needed.
  • httpClk() -> httpErrored(ss) & httpLoaded(sns)
  • deathClk() -> playerDeath(eee)
  • spawnClk() -> playerSpawn(e)
  • clk() -> ❌ Will not be replaced. Planning on reimplementing the timer library with function lambdas.
  • playerConnectClk() -> playerConnected(e)
  • playerDisconnectClk() -> playerDisconnected(e)
  • last() -> removed(n)
  • removing() -> removed(n)
  • useClk() -> chipUsed(e)

Lua Api

These events can be called on every E2 chip with E2Lib.triggerEvent(name: string, args: any[]). There is a new method on the E2 ENT, being ENT:ExecuteEvent(name: string, args: any[]) (this is what triggerEvent calls on every single chip subscribed)

To register an event, use E2Lib.registerEvent(name: string, args: string[]), for example

E2Lib.registerEvent("fileErrored", {"s", "n"}) -- string, number
E2Lib.registerEvent("fileLoaded", {"s", "s"}) -- string, string

There are constructors and destructors for handling hook creation/deletion.

E2Lib.registerEvent("chipUsed", { "e" }, function(self)
	self.entity:SetUseType(SIMPLE_USE)
	self.entity.Use = function(selfent, activator)
		self.entity:ExecuteEvent("chipUsed", { activator })
	end
end, function(self)
	self.entity.Use = nil
end)

Potential Todos

This is practically done, but there's some QoL stuff that could be done.

  • ✅ Autocomplete support
  • ❌ E2Helper docs
  • ✅ Separate highlight color (right now it's the same color as a type)
  • ❌ A way to remove events at runtime? (Would require events to become runtime constructs, which right now they are compile time. Potentially could just have them able to disable themselves temporarily.)
  • ❌ A way to trigger events by an E2? (Can be done later.)

Deprecation messages

This also shows you messages if any are provided for deprecation notices.
Nicer for dev so you don't need to jump between the function and the e2helper.

This is kind of unrelated to the PR, but I made the changes to avoid a ton of messages asking why all of the most common runOn* are deprecated now..

image

Extra autocompletion

There's now autocompletion for keywords and events. Autocompletion entries are now colored for their type (constant, function, variable, event, keyword).

Todo:
* Fix global scope variables (staying default despite being assigned to)
* Implement event arguments
* Editor highlighting
* Replaced yeet with ``event``
* Network events to client
* Editor highlighting
* Add fileLoaded and fileErrored events
@Vurv78
Copy link
Contributor Author

Vurv78 commented Oct 14, 2022

Might remove the http/file ones later, for the same reason im not implementing timers, their apis would be much better suited with a function that takes a lambda / function object

@CornerPin
Copy link
Contributor

Is it possible to assign multiple event handlers to a single event? It would be useful for libraries.

@Vurv78
Copy link
Contributor Author

Vurv78 commented Oct 14, 2022

Not right now, because I can't think of a syntax that would look decent for it

@Vurv78
Copy link
Contributor Author

Vurv78 commented Oct 14, 2022

I'm thinking of an alternative though, where you could have one event handler per file rather than per chip, so libraries would be able to handle events, it wouldn't require a new syntax for the name of the handler, and could still allow for removing the event handler in the future

@stingustooh
Copy link

this seems like it's going to be seriously inconvenient to account for in older projects. is there no way to integrate events without rendering the existing tickClk(), chatClk(), etc functions obsolete?
my immediate idea was that this may well do better as part of a new thing entirely, separate from E2.
it'll be more of a problem to have to adjust to this change than to just put up with it or make something entirely new that people can learn without replacing some of the most baseline composition of most/all existing E2s. i get that backwards compatibility is inconvenient to factor in, but it's equally inconvenient for users (especially in an otherwise fairly "casual" language and setting) to have to go back and make sure their work fits to completely new fundamental systems.

@Vurv78
Copy link
Contributor Author

Vurv78 commented Oct 16, 2022

this seems like it's going to be seriously inconvenient to account for in older projects. is there no way to integrate events without rendering the existing tickClk(), chatClk(), etc functions obsolete?

Rendering them obsolete doesn't mean it would remove them. It would just mark them deprecated and have old E2s give off hundreds of warnings. Functions that have been deprecated for over a decade still exist (case in point, e:reposition(v))

my immediate idea was that this may well do better as part of a new thing entirely, separate from E2.

Me and @Derpius already tried to make successors to E2, we came to the conclusion that it'd be way too time consuming for a gmod project, and hardly anyone would use it (they're accustomed to either E2 or StarfallEx). You can find our attempts here: Expressive and MLang. @AbigailBuccaneer brought up discussion on making an E3, but they went MIA as well.

Additionally a lot of people on the discord brought up the exact opposite of your argument, that it'd be better to just extend E2 rather than working on a new one. Since you wouldn't need to learn new syntax and every server already has it. (Also, for me, it's much easier to extend E2 than to work on a new project)

i get that backwards compatibility is inconvenient to factor in, but it's equally inconvenient for users (especially in an otherwise fairly "casual" language and setting) to have to go back and make sure their work fits to completely new fundamental systems.

Once again this wouldn't be a breaking change.

And by your argument of a "casual" language, compare these two code blocks, which one is easier to understand?
(Now imagine this, but with thousands of lines of code inside of each if statement. Yeah it's not pretty.)

@persist Var

if (chatClk()) {
    print(lastSaid(), Var)
} elseif(keyClk(owner()) == -1) {
    print("Key released")
} elseif (first()) {
   print("First")
   Var = 5
}
@persist Var

print("First")
Var = 5

event chat(Ply:entity, Said:string, Team:number) {
    print(Said, Var)
}

event keyPressed(Ply:entity, Key:string, Down:number, Bind:number) {
    if (!Down) {
        print("Key released")
    }
}

@stingustooh

@Vurv78
Copy link
Contributor Author

Vurv78 commented Oct 16, 2022

Triggers are also probably the most common things people get confused about in E2 on the discord, having this system would lead to less pointless questions and having to explain why E2 has the most complicated event system (when it could simply be replaced with a better one).

And this would stop polluting the e2helper/globals with clk* functions

@Vurv78 Vurv78 marked this pull request as draft October 29, 2022 06:33
@Vurv78
Copy link
Contributor Author

Vurv78 commented Oct 29, 2022

Just need to implement the last few clk* functions as events and separate them by file

Code is much less complicated now. Checks if you're the author of the message before letting you set the variable at all.

And now it works with the event system.
* Implement noreturn for methods
* Implement one event per-file system
* Added playerSpawn, playerDeath, playerConnected, playerDisconnected, chipUsed events
* Color code autocompletions by their type.
* Add keyword autocompletion
* Add constructor/destructor system so that events can create and remove hooks as necessary per chip.
@Vurv78 Vurv78 marked this pull request as ready for review November 27, 2022 00:40
@Vurv78
Copy link
Contributor Author

Vurv78 commented Nov 27, 2022

This is ready, would appreciate testing and feedback, especially on the bonus autocomplete stuff, and using this for libraries (multiple event handlers across files).

@Vurv78 Vurv78 merged commit 6e0212b into wiremod:master Nov 27, 2022
@Vurv78 Vurv78 mentioned this pull request Nov 27, 2022
2 tasks
@abirduphigh
Copy link

I'm having trouble with the playerConnected(e) event. Does it need an entity variable to detect if that entity has connected? If so, I don't quite understand how that works, logically or practically. The runOnPlayerConnect(n) function appeared to set up a simple listener for anyone joining the server, which is useful.

My scenario is that I want to run code only exactly when the number of players on the server changes, without needing a timer constantly running to retrieve a value from players():count(). How do I achieve that with events?

@Vurv78
Copy link
Contributor Author

Vurv78 commented Jan 6, 2023

As the name suggests, they're event listeners, not functions. See the examples on the pr and on the wiki.

All events are direct replacements of current runOn* functions and the parameters correspond to their clk() functions that return different arguments of the event, for example lastSaid() is passed as a string to the chat event.

So you can think of events as basically this, but at compile time and much more efficient.

runOnChat(1)

function chat(Msg:string) {
    print("Sent a message:", Msg)
}

if(chatClk()) {
    chat(lastSaid())
    exit()
}

@abirduphigh
Copy link

Thanks so much for taking the time to respond, @Vurv78. I was confused at first about how to actually use the new event listeners syntactically in my E2. Now I see it's much like creating an E2 function. So, the following code now gives me the intended result, and much more elegantly than any method involving the runOnPlayerConnect() function:

event playerConnected( Ply:entity ) {
    print( Ply:name() + " connected." )
}

Thanks again for helping me out, and for continuing to support and improve E2!

@quantumdude836
Copy link

Is it intentional that you can't return from an event handler, e.g. to early-exit out of the tick event?

@Vurv78
Copy link
Contributor Author

Vurv78 commented Feb 8, 2023

Is it intentional that you can't return from an event handler, e.g. to early-exit out of the tick event?

Yes, for two reasons

  1. To reserve behavior for when events are able to return values for certain things, e.g. returning a string in the chat event to override what the player is saying.

  2. For the same reason as above, but specifically to not allow returning multiple types. I still haven't thought of what is gonna be done about a strictly typed hooks-api since they kind of depend on being optional returns, which would be fine if E2 was never able to call an event, but I'm also future-proofing it for the case a function is added to execute an event from E2.

Right now you can use the exit() function, it would have the exact same functionality. Maybe in the future continue or another construct could be supported.

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

Successfully merging this pull request may close these issues.

5 participants